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