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