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