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