5674e247e30cb20ab96aad9bc9bf8df67c911c4d
[aaf/authz.git] / auth / auth-deforg / src / main / java / org / onap / aaf / org / DefaultOrg.java
1 /*******************************************************************************
2  * ============LICENSE_START====================================================
3  * * org.onap.aaf
4  * * ===========================================================================
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * * ===========================================================================
7  * * Licensed under the Apache License, Version 2.0 (the "License");
8  * * you may not use this file except in compliance with the License.
9  * * You may obtain a copy of the License at
10  * * 
11  *  *      http://www.apache.org/licenses/LICENSE-2.0
12  * * 
13  *  * Unless required by applicable law or agreed to in writing, software
14  * * distributed under the License is distributed on an "AS IS" BASIS,
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * * See the License for the specific language governing permissions and
17  * * limitations under the License.
18  * * ============LICENSE_END====================================================
19  * *
20  * *
21  ******************************************************************************/
22 package org.onap.aaf.org;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.regex.Pattern;
33
34 import javax.mail.Address;
35 import javax.mail.Message;
36 import javax.mail.MessagingException;
37 import javax.mail.Session;
38 import javax.mail.Transport;
39 import javax.mail.internet.InternetAddress;
40 import javax.mail.internet.MimeMessage;
41
42 import org.onap.aaf.auth.env.AuthzTrans;
43 import org.onap.aaf.auth.org.EmailWarnings;
44 import org.onap.aaf.auth.org.Executor;
45 import org.onap.aaf.auth.org.Organization;
46 import org.onap.aaf.auth.org.OrganizationException;
47 import org.onap.aaf.cadi.util.FQI;
48 import org.onap.aaf.misc.env.Env;
49
50 public class DefaultOrg implements Organization {
51         private static final String AAF_DATA_DIR = "aaf_data_dir";
52         private static final String PROPERTY_IS_REQUIRED = " property is Required";
53         // Package on Purpose
54         final String domain;
55         final String atDomain;
56         final String realm;
57         
58         private final String NAME,mailHost,mailFrom;
59
60         public DefaultOrg(Env env, String realm) throws OrganizationException {
61                 this.realm = realm;
62                 domain=FQI.reverseDomain(realm);
63                 atDomain = '@'+domain;
64                 String s;
65                 NAME=env.getProperty(realm + ".name","Default Organization");
66                 mailHost = env.getProperty(s=(realm + ".mailHost"), null);
67                 if(mailHost==null) {
68                         throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
69                 }
70                 mailFrom = env.getProperty(s=(realm + ".mailFrom"), null);
71                 if(mailFrom==null) {
72                         throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
73                 }
74                 
75                 System.getProperties().setProperty("mail.smtp.host",mailHost);
76                 System.getProperties().setProperty("mail.user", mailFrom);
77                 // Get the default Session object.
78                 session = Session.getDefaultInstance(System.getProperties());
79
80                 try {
81                         String defFile;
82                         String temp=env.getProperty(defFile = (getClass().getName()+".file"));
83                         File fIdentities=null;
84                         if(temp==null) {
85                                 temp = env.getProperty(AAF_DATA_DIR);
86                                 if(temp!=null) {
87                                         env.warn().log(defFile, " is not defined. Using default: ",temp+"/identities.dat");
88                                         File dir = new File(temp);
89                                         fIdentities=new File(dir,"identities.dat");
90                                         if(!fIdentities.exists()) {
91                                                 env.warn().log("No",fIdentities.getCanonicalPath(),"exists.  Creating.");
92                                                 if(!dir.exists()) {
93                                                         dir.mkdirs();
94                                                 }
95                                                 fIdentities.createNewFile();
96                                         }
97                                 }
98                         } else {
99                                 fIdentities = new File(temp);
100                                 if(!fIdentities.exists()) {
101                                         String dataDir = env.getProperty(AAF_DATA_DIR);
102                                         if(dataDir!=null) {
103                                                 fIdentities = new File(dataDir,temp);
104                                         }
105                                 }
106                         }
107                         
108                         if(fIdentities!=null && fIdentities.exists()) {
109                                 identities = new Identities(fIdentities);
110                         } else {
111                                 if(fIdentities==null) {
112                                         throw new OrganizationException("No Identities");
113                                 } else {
114                                         throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
115                                 }
116                         }
117                 } catch (IOException e) {
118                         throw new OrganizationException(e);
119                 }
120         }
121         
122         // Implement your own Delegation System
123         static final List<String> NULL_DELEGATES = new ArrayList<String>();
124
125         public Identities identities;
126         private boolean dryRun;
127         private Session session;
128         public enum Types {Employee, Contractor, Application, NotActive};
129         private final static Set<String> typeSet;
130         
131         static {
132                 typeSet = new HashSet<String>();
133                 for(Types t : Types.values()) {
134                         typeSet.add(t.name());
135                 }
136         }
137         
138         private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
139
140         @Override
141         public String getName() {
142                 return NAME;
143         }
144
145         @Override
146         public String getRealm() {
147                 return realm;
148         }
149
150         @Override
151         public String getDomain() {
152                 return domain;
153         }
154
155         @Override
156         public DefaultOrgIdentity getIdentity(AuthzTrans trans, String id) throws OrganizationException {
157                 int at = id.indexOf('@');
158                 return new DefaultOrgIdentity(trans,at<0?id:id.substring(0, at),this);
159         }
160
161         // Note: Return a null if found; return a String Message explaining why not found. 
162         @Override
163         public String isValidID(final AuthzTrans trans, final String id) {
164                 try {
165                         DefaultOrgIdentity u = getIdentity(trans,id);
166                         return (u==null||!u.isFound())?id + "is not an Identity in " + getName():null;
167                 } catch (OrganizationException e) {
168                         return getName() + " could not lookup " + id + ": " + e.getLocalizedMessage();
169                 }
170         }
171         // Possible ID Pattern
172         //      private static final Pattern ID_PATTERN=Pattern.compile("([\\w.-]+@[\\w.-]+).{4-13}");
173         // Another one: ID_PATTERN = "(a-z[a-z0-9]{5-8}@.*).{4-13}";
174
175         @Override
176         public boolean isValidCred(final AuthzTrans trans, final String id) {
177                 // have domain?
178                 int at = id.indexOf('@');
179                 String sid;
180                 if(at > 0) {
181                         // Use this to prevent passwords to any but THIS domain.
182 //                      if(!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
183 //                              return false;
184 //                      }
185                         sid = id.substring(0,at); 
186                 } else {
187                         sid = id;
188                 }
189                 // We'll validate that it exists, rather than check patterns.
190                 
191                 return isValidID(trans, sid)==null;
192                 // Check Pattern (if checking existing is too long)
193                 //              if(id.endsWith(SUFFIX) && ID_PATTERN.matcher(id).matches()) {
194                 //                      return true;
195                 //              }
196                 //              return false; 
197         }
198
199         private static final String SPEC_CHARS = "!@#$%^*-+?/,:;.";
200         private static final Pattern PASS_PATTERN=Pattern.compile("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[" + SPEC_CHARS +"]).{6,20})");
201         /**
202          *  Attribution: from mkyong.com
203          *  (                           # Start of group
204          *  (?=.*\d)                    #   must contains one digit from 0-9
205          *  (?=.*[a-z])         #   must contains one lowercase characters
206          *  (?=.*[A-Z])         #   must contains one uppercase characters
207          *  (?=.*[@#$%])                #   must contains one special symbols in the list SPEC_CHARS
208          *              .               #     match anything with previous condition checking
209          *          {6,20}      #        length at least 6 characters and maximum of 20
210          *  )                           # End of group
211          */
212         @Override
213         public String isValidPassword(final AuthzTrans trans, final String user, final String password, final String... prev) {
214                 for(String p : prev) {
215                         if(password.contains(p)) { // A more sophisticated algorithm might be better.
216                                 return "Password too similar to previous passwords";
217                         }
218                 }
219                 // If you have an Organization user/Password scheme, replace the following
220                 if(PASS_PATTERN.matcher(password).matches()) {
221                         return "";
222                 }
223                 return "Password does not match " + NAME + " Password Standards";
224         }
225
226         private static final String[] rules = new String[] {
227                         "Passwords must contain one digit from 0-9",
228                         "Passwords must contain one lowercase character",
229                         "Passwords must contain one uppercase character",
230                         "Passwords must contain one special symbols in the list \""+ SPEC_CHARS + '"',
231                         "Passwords must be between 6 and 20 chars in length"
232         };
233
234         @Override
235         public String[] getPasswordRules() {
236                 return rules;
237         }
238
239         @Override
240         public Set<String> getIdentityTypes() {
241                 return typeSet;
242         }
243
244         @Override
245         public Response notify(AuthzTrans trans, Notify type, String url, String[] identities, String[] ccs, String summary, Boolean urgent) {
246                 String system = trans.getProperty("CASS_ENV", "");
247
248                 ArrayList<String> toList = new ArrayList<String>();
249                 Identity identity;
250                 if (identities != null) {
251                         for (String user : identities) {
252                                 try {
253                                         identity = getIdentity(trans, user);
254                                         if (identity == null) {
255                                                 trans.error().log(
256                                                                 "Failure to obtain User " + user + " for "
257                                                                                 + getName());
258                                         } else {
259                                                 toList.add(identity.email());
260                                         }
261                                 } catch (Exception e) {
262                                         trans.error().log(
263                                                         e,
264                                                         "Failure to obtain User " + user + " for "
265                                                                         + getName());
266                                 }
267                         }
268                 }
269
270                 if (toList.isEmpty()) {
271                         trans.error().log("No Users listed to email");
272                         return Response.ERR_NotificationFailure;
273                 }
274
275                 ArrayList<String> ccList = new ArrayList<String>();
276
277                 // If we're sending an urgent email, CC the user's supervisor
278                 //
279                 if (urgent) {
280                         trans.info().log("urgent msg for: " + identities[0]);
281                         try {
282                                 List<Identity> supervisors = getApprovers(trans, identities[0]);
283                                 for (Identity us : supervisors) {
284                                         trans.info().log("supervisor: " + us.email());
285                                         ccList.add(us.email());
286                                 }
287                         } catch (Exception e) {
288                                 trans.error().log(e,
289                                                 "Failed to find supervisor for  " + identities[0]);
290                         }
291                 }
292
293                 if (ccs != null) {
294                         for (String user : ccs) {
295                                 try {
296                                         identity = getIdentity(trans, user);
297                                         ccList.add(identity.email());
298                                 } catch (Exception e) {
299                                         trans.error().log(
300                                                         e,
301                                                         "Failure to obtain User " + user + " for "
302                                                                         + getName());
303                                 }
304                         }
305                 }
306
307                 if (summary == null) {
308                         summary = "";
309                 }
310
311                 switch (type) {
312                 case Approval:
313                         try {
314                                 sendEmail(trans, toList, ccList,
315                                                 "AAF Approval Notification "
316                                                                 + (system.length() == 0 ? "" : "(ENV: "
317                                                                                 + system + ")"),
318                                                 "AAF is the "
319                                                 + NAME
320                                                 + "System for Fine-Grained Authorizations.  You are being asked to Approve"
321                                                                 + (system.length() == 0 ? "" : " in the "
322                                                                                 + system + " environment")
323                                                                 + " before AAF Actions can be taken.\n\n"
324                                                                 + "Please follow this link: \n\n\t" + url
325                                                                 + "\n\n" + summary, urgent);
326                         } catch (Exception e) {
327                                 trans.error().log(e, "Failure to send Email");
328                                 return Response.ERR_NotificationFailure;
329                         }
330                         break;
331                 case PasswordExpiration:
332                         try {
333                                 sendEmail(trans,
334                                                 toList,
335                                                 ccList,
336                                                 "AAF Password Expiration Warning "
337                                                                 + (system.length() == 0 ? "" : "(ENV: "
338                                                                                 + system + ")"),
339                                                 "AAF is the "
340                                                 + NAME
341                                                 + " System for Authorizations.\n\nOne or more passwords will expire soon or have expired"
342                                                                 + (system.length() == 0 ? "" : " in the "
343                                                                                 + system + " environment")
344                                                                 + ".\n\nPasswords expired for more than 30 days without action are subject to deletion.\n\n"
345                                                                 + "Please follow each link to add a New Password with Expiration Date. Either are valid until expiration. "
346                                                                 + "Use this time to change the passwords on your system. If issues, reply to this email.\n\n"
347                                                                 + summary, urgent);
348                         } catch (Exception e) {
349                                 trans.error().log(e, "Failure to send Email");
350                                 return Response.ERR_NotificationFailure;
351                         }
352                         break;
353
354                 case RoleExpiration:
355                         try {
356                                 sendEmail(
357                                                 trans,
358                                                 toList,
359                                                 ccList,
360                                                 "AAF Role Expiration Warning "
361                                                                 + (system.length() == 0 ? "" : "(ENV: "
362                                                                                 + system + ")"),
363                                                 "AAF is the "
364                                                 + NAME
365                                                 + " System for Authorizations. One or more roles will expire soon"
366                                                                 + (system.length() == 0 ? "" : " in the "
367                                                                                 + system + " environment")
368                                                                 + ".\n\nRoles expired for more than 30 days are subject to deletion."
369                                                                 + "Please follow this link the GUI Command line, and either 'extend' or 'del' the user in the role.\n"
370                                                                 + "If issues, reply to this email.\n\n\t" + url
371                                                                 + "\n\n" + summary, urgent);
372                         } catch (Exception e) {
373                                 trans.error().log(e, "Failure to send Email");
374                                 return Response.ERR_NotificationFailure;
375                         }
376                         break;
377                 default:
378                         return Response.ERR_NotImplemented;
379                 }
380                 return Response.OK;
381         }
382
383         @Override
384         public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
385                         Boolean urgent) throws OrganizationException {
386                 int status = 1;
387                 
388                 List<String> to = new ArrayList<String>();
389                 for(String em : toList) {
390                         if(em.indexOf('@')<0) {
391                                 to.add(new DefaultOrgIdentity(trans, em, this).email());
392                         } else {
393                                 to.add(em);
394                         }
395                 }
396                 
397                 List<String> cc = new ArrayList<String>();
398                 if(ccList!=null) {
399                         if(!ccList.isEmpty()) {
400                         
401                                 for(String em : ccList) {
402                                         if(em.indexOf('@')<0) {
403                                                 cc.add(new DefaultOrgIdentity(trans, em, this).email());
404                                         } else {
405                                                 cc.add(em);
406                                         }
407                                 }
408                         }
409                         
410                         // for now, I want all emails so we can see what goes out. Remove later
411                         if (!ccList.contains(mailFrom)) {
412                                 ccList.add(mailFrom);
413                         }
414                 }
415
416                 try {
417                         // Create a default MimeMessage object.
418                         MimeMessage message = new MimeMessage(session);
419
420                         // Set From: header field of the header.
421                         message.setFrom(new InternetAddress(mailFrom));
422
423                         if (!dryRun) {
424                                 // Set To: header field of the header. This is a required field
425                                 // and calling module should make sure that it is not null or
426                                 // blank
427                                 message.addRecipients(Message.RecipientType.TO,getAddresses(to));
428
429                                 // Set CC: header field of the header.
430                                 if ((ccList != null) && (ccList.size() > 0)) {
431                                         message.addRecipients(Message.RecipientType.CC,getAddresses(cc));
432                                 }
433
434                                 // Set Subject: header field
435                                 message.setSubject(subject);
436
437                                 if (urgent) {
438                                         message.addHeader("X-Priority", "1");
439                                 }
440
441                                 // Now set the actual message
442                                 message.setText(body);
443                         } else {
444                                 // override recipients
445                                 message.addRecipients(Message.RecipientType.TO,
446                                                 InternetAddress.parse(mailFrom));
447
448                                 // Set Subject: header field
449                                 message.setSubject("[TESTMODE] " + subject);
450
451                                 if (urgent) {
452                                         message.addHeader("X-Priority", "1");
453                                 }
454
455                                 ArrayList<String> newBody = new ArrayList<String>();
456
457                                 Address temp[] = getAddresses(to);
458                                 String headerString = "TO:\t" + InternetAddress.toString(temp) + "\n";
459
460                                 temp = getAddresses(cc);
461                                 headerString += "CC:\t" + InternetAddress.toString(temp) + "\n";
462
463                                 newBody.add(headerString);
464
465                                 newBody.add("Text: \n");
466
467                                 newBody.add(body);
468                                 String outString = "";
469                                 for (String s : newBody) {
470                                         outString += s + "\n";
471                                 }
472
473                                 message.setText(outString);
474                         }
475                         // Send message
476                         Transport.send(message);
477                         status = 0;
478
479                 } catch (MessagingException mex) {
480                         throw new OrganizationException("Exception send email message "
481                                         + mex.getMessage());
482                 }
483
484                 return status;  
485         }
486
487         /**
488          * Default Policy is to set to 6 Months for Notification Types.
489          * add others/change as required
490          */
491         @Override
492         public Date whenToValidate(Notify type, Date lastValidated) {
493                 switch(type) {
494                         case Approval:
495                         case PasswordExpiration:
496                                 return null;
497                         default:
498                                 GregorianCalendar gc = new GregorianCalendar();
499                                 gc.setTime(lastValidated);
500                                 gc.add(GregorianCalendar.MONTH, 6);  // 6 month policy
501                                 return gc.getTime();
502                 }
503         }
504
505         @Override
506         public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
507         GregorianCalendar now = new GregorianCalendar();
508         GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
509                 switch (exp) {
510                         case ExtendPassword:
511                                 // Extending Password give 5 extra days, max 8 days from now
512                                 rv.add(GregorianCalendar.DATE, 5);
513                                 now.add(GregorianCalendar.DATE, 8);
514                                 if(rv.after(now)) {
515                                         rv = now;
516                                 }
517                                 break;
518                         case Future:
519                                 // Future requests last 15 days.
520                                 now.add(GregorianCalendar.DATE, 15);
521                                 rv = now;
522                                 break;
523                         case Password:
524                                 // Passwords expire in 90 days
525                                 now.add(GregorianCalendar.DATE, 90);
526                                 rv = now;
527                                 break;
528                         case TempPassword:
529                                 // Temporary Passwords last for 12 hours.
530                                 now.add(GregorianCalendar.DATE, 90);
531                                 rv = now;
532                                 break;
533                         case UserDelegate:
534                                 // Delegations expire max in 2 months, renewable to 3
535                                 rv.add(GregorianCalendar.MONTH, 2);
536                                 now.add(GregorianCalendar.MONTH, 3);
537                                 if(rv.after(now)) {
538                                         rv = now;
539                                 }
540                                 break;
541                         case UserInRole:
542                                 // Roles expire in 6 months
543                                 now.add(GregorianCalendar.MONTH, 6);
544                                 rv = now;
545                                 break;
546                         default:
547                                 // Unless other wise set, 6 months is default
548                                 now.add(GregorianCalendar.MONTH, 6);
549                                 rv = now;
550                                 break;
551                 }
552                 return rv;
553         }
554
555         @Override
556         public EmailWarnings emailWarningPolicy() {
557                 return emailWarnings;
558         }
559
560         /**
561          * Assume the Supervisor is the Approver.
562          */
563         @Override
564         public List<Identity> getApprovers(AuthzTrans trans, String user) throws OrganizationException {
565                 Identity orgIdentity = getIdentity(trans, user);
566                 List<Identity> orgIdentitys = new ArrayList<Identity>();
567                 if(orgIdentity!=null) {
568                         Identity supervisor = orgIdentity.responsibleTo();
569                         if(supervisor!=null) {
570                                 orgIdentitys.add(supervisor);
571                         }
572                 }
573                 return orgIdentitys;    
574         }
575
576         @Override
577         public String getApproverType() {
578                 return "supervisor";
579         }
580
581         @Override
582         public int startOfDay() {
583                 // TODO Auto-generated method stub
584                 return 0;
585         }
586
587         @Override
588         public boolean canHaveMultipleCreds(String id) {
589                 // External entities are likely mono-password... if you change it, it is a global change.
590                 // This is great for people, but horrible for Applications.  
591                 //
592                 // AAF's Password can have multiple Passwords, each with their own Expiration Date.
593                 // For Default Org, we'll assume true for all, but when you add your external
594                 // Identity stores, you need to return "false" if they cannot support multiple Passwords like AAF
595                 return true;
596         }
597
598         @Override
599         public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
600                 switch(policy) {
601                         case OWNS_MECHID:
602                         case CREATE_MECHID:
603                                 if(vars.length>0) {
604                                         DefaultOrgIdentity thisID = getIdentity(trans,vars[0]);
605                                         if("a".equals(thisID.identity.status)) { // MechID
606                                                 DefaultOrgIdentity requestor = getIdentity(trans, trans.user());
607                                                 if(requestor!=null) {
608                                                         Identity mechid = getIdentity(trans, vars[0]);
609                                                         if(mechid!=null) {
610                                                                 Identity sponsor = mechid.responsibleTo();
611                                                                 if(sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
612                                                                         return null;
613                                                                 } else {
614                                                                         return trans.user() + " is not the Sponsor of MechID " + vars[0];
615                                                                 }
616                                                         }
617                                                 }
618                                         }
619                                 }
620                                 return null;
621                                 
622                         case CREATE_MECHID_BY_PERM_ONLY:
623                                 return getName() + " only allows sponsors to create MechIDs";
624                                 
625                         default:
626                                 return policy.name() + " is unsupported at " + getName();
627                 }       
628         }
629
630         @Override
631         public boolean isTestEnv() {
632                 return false;
633         }
634
635         @Override
636         public void setTestMode(boolean dryRun) {
637                 this.dryRun = dryRun;
638         }
639
640         /**
641          * Convert the delimiter String into Internet addresses with the default
642          * delimiter of ";"
643          * @param strAddress
644          * @return
645          */
646         private Address[] getAddresses(List<String> strAddress) throws OrganizationException {
647                 return this.getAddresses(strAddress,";");
648         }
649         /**
650          * Convert the delimiter String into Internet addresses with the 
651          * delimiter of provided
652          * @param strAddress
653          * @param delimiter
654          * @return
655          */
656         private Address[] getAddresses(List<String> strAddresses, String delimiter) throws OrganizationException {
657                 Address[] addressArray = new Address[strAddresses.size()];
658                 int count = 0;
659                 for (String addr : strAddresses)
660                 {
661             try{
662                 addressArray[count] = new InternetAddress(addr);
663                 count++;
664             }catch(Exception e){
665                 throw new OrganizationException("Failed to parse the email address "+ addr +": "+e.getMessage());
666             }
667         }
668         return addressArray;
669         }
670
671                         
672         }