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