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