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