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