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.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;
46 public class DefaultOrg implements Organization {
47 private static final String AAF_DATA_DIR = "aaf_data_dir";
50 final String atDomain;
53 private final String root_ns;
55 private final String NAME;
56 private final Set<String> supportedRealms;
60 public DefaultOrg(Env env, String realm) throws OrganizationException {
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);
72 String temp=env.getProperty(defFile = (getClass().getName()+".file"));
73 File fIdentities=null;
75 temp = env.getProperty(AAF_DATA_DIR);
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");
81 if (!fIdentities.exists()) {
82 env.warn().log("No",fIdentities.getCanonicalPath(),"exists. Creating.");
86 fIdentities.createNewFile();
91 fIdentities = new File(temp);
92 if (!fIdentities.exists()) {
93 String dataDir = env.getProperty(AAF_DATA_DIR);
95 fIdentities = new File(dataDir,temp);
100 if (fIdentities!=null && fIdentities.exists()) {
101 identities = new Identities(fIdentities);
103 if (fIdentities==null) {
104 throw new OrganizationException("No Identities: set \"" + AAF_DATA_DIR + '"');
106 throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
111 temp=env.getProperty(getClass().getName()+".file.revoked");
113 temp = env.getProperty(AAF_DATA_DIR);
115 File dir = new File(temp);
116 fRevoked=new File(dir,"revoked.dat");
119 fRevoked = new File(temp);
121 if (fRevoked!=null && fRevoked.exists()) {
122 revoked = new Identities(fRevoked);
127 } catch (IOException e) {
128 throw new OrganizationException(e);
132 // Implement your own Delegation System
133 static final List<String> NULL_DELEGATES = new ArrayList<>();
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;
143 typeSet = new HashSet<>();
144 for (Types t : Types.values()) {
145 typeSet.add(t.name());
149 private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
152 public String getName() {
157 public String getRealm() {
162 public String getDomain() {
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);
173 * If the ID isn't in the revoked file, if it exists, it is revoked.
176 public Date isRevoked(AuthzTrans trans, String key) {
179 revoked.open(trans, DefaultOrgIdentity.TIMEOUT);
181 Reuse r = revoked.reuse();
182 int at = key.indexOf(domain);
185 search = key.substring(0,at);
189 Data revokedData = revoked.find(search, r);
190 return revokedData==null?null:new Date();
192 revoked.close(trans);
194 } catch (IOException e) {
195 trans.error().log(e);
202 * @see org.onap.aaf.auth.org.Organization#getEsclaations(org.onap.aaf.auth.env.AuthzTrans, java.lang.String, int)
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));
209 for(int i=0;i<end;++i) {
211 id = getIdentity(trans,user);
213 id = id.responsibleTo();
224 // Note: Return a null if found; return a String Message explaining why not found.
226 public String isValidID(final AuthzTrans trans, final String id) {
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();
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}";
239 public boolean isValidCred(final AuthzTrans trans, final String id) {
241 int at = id.indexOf('@');
244 // Use this to prevent passwords to any but THIS domain.
245 // if (!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
248 sid = id.substring(0,at);
252 // We'll validate that it exists, rather than check patterns.
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()) {
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})");
266 * (?=.*[a-z,A-Z]) # must contain one character
268 * (?=.*\d) # must contain one digit from 0-9
270 * (?=.*[@#$%]) # must contain one special symbols in the list SPEC_CHARS
272 * . # match anything with previous condition checking
273 * {6,20} # length at least 6 characters and maximum of 20
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
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
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";
295 // If you have an Organization user/Password scheme, replace the following
296 if (PASS_PATTERN.matcher(password).matches()) {
299 return "Password does not match " + NAME + " Password Standards";
302 private static final String[] rules = new String[] {
303 "Passwords must contain letters",
304 "Passwords must contain one of the following:",
306 " One special symbols in the list \""+ SPEC_CHARS + '"',
307 "Passwords must be between 6 and 20 chars in length",
311 public String[] getPasswordRules() {
316 public Set<String> getIdentityTypes() {
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", "");
324 ArrayList<String> toList = new ArrayList<>();
326 if (identities != null) {
327 for (String user : identities) {
329 identity = getIdentity(trans, user);
330 if (identity == null) {
332 "Failure to obtain User " + user + " for "
335 toList.add(identity.email());
337 } catch (Exception e) {
340 "Failure to obtain User " + user + " for "
346 if (toList.isEmpty()) {
347 trans.error().log("No Users listed to email");
348 return Response.ERR_NotificationFailure;
351 ArrayList<String> ccList = new ArrayList<>();
353 // If we're sending an urgent email, CC the user's supervisor
356 trans.info().log("urgent msg for: " + identities[0]);
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());
363 } catch (Exception e) {
365 "Failed to find supervisor for " + identities[0]);
370 for (String user : ccs) {
372 identity = getIdentity(trans, user);
373 ccList.add(identity.email());
374 } catch (Exception e) {
377 "Failure to obtain User " + user + " for "
383 if (summary == null) {
390 sendEmail(trans, toList, ccList,
391 "AAF Approval Notification "
392 + (system.length() == 0 ? "" : "(ENV: "
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) {
404 trans.error().log(e, "Failure to send Email");
405 return Response.ERR_NotificationFailure;
408 case PasswordExpiration:
413 "AAF Password Expiration Warning "
414 + (system.length() == 0 ? "" : "(ENV: "
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"
425 } catch (Exception e) {
426 trans.error().log(e, "Failure to send Email");
427 return Response.ERR_NotificationFailure;
437 "AAF Role Expiration Warning "
438 + (system.length() == 0 ? "" : "(ENV: "
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;
455 return Response.ERR_NotImplemented;
462 * Default Policy is to set to 6 Months for Notification Types.
463 * add others/change as required
466 public Date whenToValidate(Notify type, Date lastValidated) {
469 case PasswordExpiration:
472 GregorianCalendar gc = new GregorianCalendar();
473 gc.setTime(lastValidated);
474 gc.add(GregorianCalendar.MONTH, 6); // 6 month policy
480 public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
481 GregorianCalendar now = new GregorianCalendar();
482 GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
485 // Extending Password give 5 extra days, max 8 days from now
486 rv.add(GregorianCalendar.DATE, 5);
487 now.add(GregorianCalendar.DATE, 8);
493 // Future requests last 15 days.
494 now.add(GregorianCalendar.DATE, 15);
498 // Passwords expire in 90 days
499 now.add(GregorianCalendar.DATE, 90);
503 // Temporary Passwords last for 12 hours.
504 now.add(GregorianCalendar.DATE, 90);
508 // Delegations expire max in 2 months, renewable to 3
509 rv.add(GregorianCalendar.MONTH, 2);
510 now.add(GregorianCalendar.MONTH, 3);
516 // Roles expire in 6 months
517 now.add(GregorianCalendar.MONTH, 6);
520 case RevokedGracePeriodEnds:
521 now.add(GregorianCalendar.DATE, 3);
525 // Unless other wise set, 6 months is default
526 now.add(GregorianCalendar.MONTH, 6);
534 public EmailWarnings emailWarningPolicy() {
535 return emailWarnings;
539 * Assume the Supervisor is the Approver.
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);
555 public String getApproverType() {
560 public int startOfDay() {
561 // TODO Auto-generated method stub
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.
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
577 public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
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]);
589 Identity sponsor = mechid.responsibleTo();
590 if (sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
593 return trans.user() + " is not the Sponsor of MechID " + vars[0];
601 case CREATE_MECHID_BY_PERM_ONLY:
602 return getName() + " only allows sponsors to create MechIDs";
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();
611 return policy.name() + " is unsupported at " + getName();
616 public boolean isTestEnv() {
621 public void setTestMode(boolean dryRun) {
622 this.dryRun = dryRun;
625 private String extractRealm(final String r) {
627 if ((at=r.indexOf('@'))>=0) {
628 return FQI.reverseDomain(r.substring(at+1));
633 public boolean supportsRealm(final String r) {
634 if (r.endsWith(realm)) {
637 String erealm = extractRealm(r);
638 for (String sr : supportedRealms) {
639 if (erealm.startsWith(sr)) {
648 public String supportedDomain(String user) {
650 int after_at = user.indexOf('@')+1;
651 if(after_at<user.length()) {
652 String ud = FQI.reverseDomain(user);
653 if(ud.startsWith(getDomain())) {
656 for(String s : supportedRealms) {
657 if(ud.startsWith(s)) {
658 return FQI.reverseDomain(s);
667 public synchronized void addSupportedRealm(final String r) {
668 supportedRealms.add(extractRealm(r));
672 public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
673 Boolean urgent) throws OrganizationException {
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());
685 List<String> cc = new ArrayList<>();
687 if (!ccList.isEmpty()) {
689 for (String em : ccList) {
690 if (em.indexOf('@')<0) {
691 cc.add(new DefaultOrgIdentity(trans, em, this).email());
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);
704 return mailer.sendEmail(trans,dryRun?"DefaultOrg":null,to,cc,subject,body,urgent)?0:1;