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