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