1 /*******************************************************************************
2 * ============LICENSE_START====================================================
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
11 * * http://www.apache.org/licenses/LICENSE-2.0
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====================================================
21 ******************************************************************************/
22 package org.onap.aaf.org;
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;
32 import java.util.regex.Pattern;
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;
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";
48 final String atDomain;
51 private final String NAME,mailHost,mailFrom;
52 private final Set<String> supportedRealms;
55 public DefaultOrg(Env env, String realm) throws OrganizationException {
58 supportedRealms=new HashSet<>();
59 supportedRealms.add(realm);
60 domain=FQI.reverseDomain(realm);
61 atDomain = '@'+domain;
63 NAME=env.getProperty(realm + ".name","Default Organization");
64 mailHost = env.getProperty(s=(realm + ".mailHost"), null);
66 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
68 mailFrom = env.getProperty(s=(realm + ".mailFrom"), null);
70 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
73 // Note: This code is to avoid including javax.mail into ONAP, because there are security/licence
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");
84 System.getProperties().setProperty("mail.smtp.host",mailHost);
85 System.getProperties().setProperty("mail.user", mailFrom);
89 String temp=env.getProperty(defFile = (getClass().getName()+".file"));
90 File fIdentities=null;
92 temp = env.getProperty(AAF_DATA_DIR);
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");
98 if(!fIdentities.exists()) {
99 env.warn().log("No",fIdentities.getCanonicalPath(),"exists. Creating.");
103 fIdentities.createNewFile();
107 fIdentities = new File(temp);
108 if(!fIdentities.exists()) {
109 String dataDir = env.getProperty(AAF_DATA_DIR);
111 fIdentities = new File(dataDir,temp);
116 if(fIdentities!=null && fIdentities.exists()) {
117 identities = new Identities(fIdentities);
119 if(fIdentities==null) {
120 throw new OrganizationException("No Identities");
122 throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
125 } catch (IOException e) {
126 throw new OrganizationException(e);
130 // Implement your own Delegation System
131 static final List<String> NULL_DELEGATES = new ArrayList<>();
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;
140 typeSet = new HashSet<>();
141 for(Types t : Types.values()) {
142 typeSet.add(t.name());
146 private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
149 public String getName() {
154 public String getRealm() {
159 public String getDomain() {
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);
169 // Note: Return a null if found; return a String Message explaining why not found.
171 public String isValidID(final AuthzTrans trans, final String id) {
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();
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}";
184 public boolean isValidCred(final AuthzTrans trans, final String id) {
186 int at = id.indexOf('@');
189 // Use this to prevent passwords to any but THIS domain.
190 // if(!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
193 sid = id.substring(0,at);
197 // We'll validate that it exists, rather than check patterns.
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()) {
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})");
211 * (?=.*[a-z,A-Z]) # must contain one character
213 * (?=.*\d) # must contain one digit from 0-9
215 * (?=.*[@#$%]) # must contain one special symbols in the list SPEC_CHARS
217 * . # match anything with previous condition checking
218 * {6,20} # length at least 6 characters and maximum of 20
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
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
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";
240 // If you have an Organization user/Password scheme, replace the following
241 if(PASS_PATTERN.matcher(password).matches()) {
244 return "Password does not match " + NAME + " Password Standards";
247 private static final String[] rules = new String[] {
248 "Passwords must contain letters",
249 "Passwords must contain one of the following:",
251 " One special symbols in the list \""+ SPEC_CHARS + '"',
252 "Passwords must be between 6 and 20 chars in length",
256 public String[] getPasswordRules() {
261 public Set<String> getIdentityTypes() {
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", "");
269 ArrayList<String> toList = new ArrayList<>();
271 if (identities != null) {
272 for (String user : identities) {
274 identity = getIdentity(trans, user);
275 if (identity == null) {
277 "Failure to obtain User " + user + " for "
280 toList.add(identity.email());
282 } catch (Exception e) {
285 "Failure to obtain User " + user + " for "
291 if (toList.isEmpty()) {
292 trans.error().log("No Users listed to email");
293 return Response.ERR_NotificationFailure;
296 ArrayList<String> ccList = new ArrayList<>();
298 // If we're sending an urgent email, CC the user's supervisor
301 trans.info().log("urgent msg for: " + identities[0]);
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());
308 } catch (Exception e) {
310 "Failed to find supervisor for " + identities[0]);
315 for (String user : ccs) {
317 identity = getIdentity(trans, user);
318 ccList.add(identity.email());
319 } catch (Exception e) {
322 "Failure to obtain User " + user + " for "
328 if (summary == null) {
335 sendEmail(trans, toList, ccList,
336 "AAF Approval Notification "
337 + (system.length() == 0 ? "" : "(ENV: "
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) {
349 trans.error().log(e, "Failure to send Email");
350 return Response.ERR_NotificationFailure;
353 case PasswordExpiration:
358 "AAF Password Expiration Warning "
359 + (system.length() == 0 ? "" : "(ENV: "
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"
370 } catch (Exception e) {
371 trans.error().log(e, "Failure to send Email");
372 return Response.ERR_NotificationFailure;
382 "AAF Role Expiration Warning "
383 + (system.length() == 0 ? "" : "(ENV: "
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;
400 return Response.ERR_NotImplemented;
407 * Default Policy is to set to 6 Months for Notification Types.
408 * add others/change as required
411 public Date whenToValidate(Notify type, Date lastValidated) {
414 case PasswordExpiration:
417 GregorianCalendar gc = new GregorianCalendar();
418 gc.setTime(lastValidated);
419 gc.add(GregorianCalendar.MONTH, 6); // 6 month policy
425 public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
426 GregorianCalendar now = new GregorianCalendar();
427 GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
430 // Extending Password give 5 extra days, max 8 days from now
431 rv.add(GregorianCalendar.DATE, 5);
432 now.add(GregorianCalendar.DATE, 8);
438 // Future requests last 15 days.
439 now.add(GregorianCalendar.DATE, 15);
443 // Passwords expire in 90 days
444 now.add(GregorianCalendar.DATE, 90);
448 // Temporary Passwords last for 12 hours.
449 now.add(GregorianCalendar.DATE, 90);
453 // Delegations expire max in 2 months, renewable to 3
454 rv.add(GregorianCalendar.MONTH, 2);
455 now.add(GregorianCalendar.MONTH, 3);
461 // Roles expire in 6 months
462 now.add(GregorianCalendar.MONTH, 6);
466 // Unless other wise set, 6 months is default
467 now.add(GregorianCalendar.MONTH, 6);
475 public EmailWarnings emailWarningPolicy() {
476 return emailWarnings;
480 * Assume the Supervisor is the Approver.
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);
496 public String getApproverType() {
501 public int startOfDay() {
502 // TODO Auto-generated method stub
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.
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
518 public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
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]);
529 Identity sponsor = mechid.responsibleTo();
530 if(sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
533 return trans.user() + " is not the Sponsor of MechID " + vars[0];
541 case CREATE_MECHID_BY_PERM_ONLY:
542 return getName() + " only allows sponsors to create MechIDs";
545 return policy.name() + " is unsupported at " + getName();
550 public boolean isTestEnv() {
555 public void setTestMode(boolean dryRun) {
556 this.dryRun = dryRun;
559 private String extractRealm(final String r) {
561 if((at=r.indexOf('@'))>=0) {
562 return FQI.reverseDomain(r.substring(at+1));
567 public boolean supportsRealm(final String r) {
568 if(r.endsWith(realm)) {
571 String erealm = extractRealm(r);
572 for(String sr : supportedRealms) {
573 if(erealm.startsWith(sr)) {
582 public synchronized void addSupportedRealm(final String r) {
583 supportedRealms.add(extractRealm(r));
587 public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
588 Boolean urgent) throws OrganizationException {
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());
599 List<String> cc = new ArrayList<>();
601 if(!ccList.isEmpty()) {
603 for(String em : ccList) {
604 if(em.indexOf('@')<0) {
605 cc.add(new DefaultOrgIdentity(trans, em, this).email());
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);
618 return mailer.sendEmail(trans,dryRun,mailFrom,to,cc,subject,body,urgent);