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);
71 String temp=env.getProperty(realm +".file");
72 File fIdentities=null;
74 temp = env.getProperty(AAF_DATA_DIR);
76 env.warn().log("Datafile for " + realm + " is not defined. Using default: ",temp+"/identities.dat");
77 File dir = new File(temp);
78 fIdentities=new File(dir,"identities.dat");
80 if (!fIdentities.exists()) {
81 env.warn().log("No",fIdentities.getCanonicalPath(),"exists. Creating.");
85 fIdentities.createNewFile();
90 fIdentities = new File(temp);
91 if (!fIdentities.exists()) {
92 String dataDir = env.getProperty(AAF_DATA_DIR);
94 fIdentities = new File(dataDir,temp);
99 if (fIdentities!=null && fIdentities.exists()) {
100 identities = new Identities(fIdentities);
102 if (fIdentities==null) {
103 throw new OrganizationException("No Identities: set \"" + AAF_DATA_DIR + '"');
105 throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
110 temp=env.getProperty(getClass().getName()+".file.revoked");
112 temp = env.getProperty(AAF_DATA_DIR);
114 File dir = new File(temp);
115 fRevoked=new File(dir,"revoked.dat");
118 fRevoked = new File(temp);
120 if (fRevoked!=null && fRevoked.exists()) {
121 revoked = new Identities(fRevoked);
126 } catch (IOException e) {
127 throw new OrganizationException(e);
131 // Implement your own Delegation System
132 static final List<String> NULL_DELEGATES = new ArrayList<>();
134 public Identities identities;
135 public Identities revoked;
136 private boolean dryRun;
137 private Mailer mailer;
138 public enum Types {Employee, Contractor, Application, NotActive};
139 private final static Set<String> typeSet;
142 typeSet = new HashSet<>();
143 for (Types t : Types.values()) {
144 typeSet.add(t.name());
148 private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
151 public String getName() {
156 public String getRealm() {
161 public String getDomain() {
166 public DefaultOrgIdentity getIdentity(AuthzTrans trans, String id) throws OrganizationException {
167 int at = id.indexOf('@');
168 return new DefaultOrgIdentity(trans,at<0?id:id.substring(0, at),this);
172 * If the ID isn't in the revoked file, if it exists, it is revoked.
175 public Date isRevoked(AuthzTrans trans, String key) {
178 revoked.open(trans, DefaultOrgIdentity.TIMEOUT);
180 Reuse r = revoked.reuse();
181 int at = key.indexOf(domain);
184 search = key.substring(0,at);
188 Data revokedData = revoked.find(search, r);
189 return revokedData==null?null:new Date();
191 revoked.close(trans);
193 } catch (IOException e) {
194 trans.error().log(e);
201 * @see org.onap.aaf.auth.org.Organization#getEsclaations(org.onap.aaf.auth.env.AuthzTrans, java.lang.String, int)
204 public List<Identity> getIDs(AuthzTrans trans, String user, int escalate) throws OrganizationException {
205 List<Identity> rv = new ArrayList<>();
206 int end = Math.min(3,Math.abs(escalate));
208 for(int i=0;i<end;++i) {
210 id = getIdentity(trans,user);
212 id = id.responsibleTo();
223 // Note: Return a null if found; return a String Message explaining why not found.
225 public String isValidID(final AuthzTrans trans, final String id) {
227 DefaultOrgIdentity u = getIdentity(trans,id);
228 return (u==null||!u.isFound())?id + "is not an Identity in " + getName():null;
229 } catch (OrganizationException e) {
230 return getName() + " could not lookup " + id + ": " + e.getLocalizedMessage();
233 // Possible ID Pattern
234 // private static final Pattern ID_PATTERN=Pattern.compile("([\\w.-]+@[\\w.-]+).{4-13}");
235 // Another one: ID_PATTERN = "(a-z[a-z0-9]{5-8}@.*).{4-13}";
238 public boolean isValidCred(final AuthzTrans trans, final String id) {
240 int at = id.indexOf('@');
243 // Use this to prevent passwords to any but THIS domain.
244 // if (!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
247 sid = id.substring(0,at);
251 // We'll validate that it exists, rather than check patterns.
253 return isValidID(trans, sid)==null;
254 // Check Pattern (if checking existing is too long)
255 // if (id.endsWith(SUFFIX) && ID_PATTERN.matcher(id).matches()) {
261 private static final String SPEC_CHARS = "!@#$%^*-+?/,:;.";
262 private static final Pattern PASS_PATTERN=Pattern.compile("(((?=.*[a-z,A-Z])(((?=.*\\d))|(?=.*[" + SPEC_CHARS +"]))).{6,20})");
265 * (?=.*[a-z,A-Z]) # must contain one character
267 * (?=.*\d) # must contain one digit from 0-9
269 * (?=.*[@#$%]) # must contain one special symbols in the list SPEC_CHARS
271 * . # match anything with previous condition checking
272 * {6,20} # length at least 6 characters and maximum of 20
275 * Another example, more stringent pattern
276 private static final Pattern PASS_PATTERN=Pattern.compile("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[" + SPEC_CHARS +"]).{6,20})");
277 * Attribution: from mkyong.com
279 * (?=.*\d) # must contain one digit from 0-9
280 * (?=.*[a-z]) # must contain one lowercase characters
281 * (?=.*[A-Z]) # must contain one uppercase characters
282 * (?=.*[@#$%]) # must contain one special symbols in the list SPEC_CHARS
283 * . # match anything with previous condition checking
284 * {6,20} # length at least 6 characters and maximum of 20
288 public String isValidPassword(final AuthzTrans trans, final String user, final String password, final String... prev) {
289 for (String p : prev) {
290 if (password.contains(p)) { // A more sophisticated algorithm might be better.
291 return "Password too similar to previous passwords";
294 // If you have an Organization user/Password scheme, replace the following
295 if (PASS_PATTERN.matcher(password).matches()) {
298 return "Password does not match " + NAME + " Password Standards";
301 private static final String[] rules = new String[] {
302 "Passwords must contain letters",
303 "Passwords must contain one of the following:",
305 " One special symbols in the list \""+ SPEC_CHARS + '"',
306 "Passwords must be between 6 and 20 chars in length",
310 public String[] getPasswordRules() {
315 public Set<String> getIdentityTypes() {
320 public Response notify(AuthzTrans trans, Notify type, String url, String[] identities, String[] ccs, String summary, Boolean urgent) {
321 String system = trans.getProperty("CASS_ENV", "");
323 ArrayList<String> toList = new ArrayList<>();
325 if (identities != null) {
326 for (String user : identities) {
328 identity = getIdentity(trans, user);
329 if (identity == null) {
331 "Failure to obtain User " + user + " for "
334 toList.add(identity.email());
336 } catch (Exception e) {
339 "Failure to obtain User " + user + " for "
345 if (toList.isEmpty()) {
346 trans.error().log("No Users listed to email");
347 return Response.ERR_NotificationFailure;
350 ArrayList<String> ccList = new ArrayList<>();
352 // If we're sending an urgent email, CC the user's supervisor
355 trans.info().log("urgent msg for: " + identities[0]);
357 List<Identity> supervisors = getApprovers(trans, identities[0]);
358 for (Identity us : supervisors) {
359 trans.info().log("supervisor: " + us.email());
360 ccList.add(us.email());
362 } catch (Exception e) {
364 "Failed to find supervisor for " + identities[0]);
369 for (String user : ccs) {
371 identity = getIdentity(trans, user);
372 ccList.add(identity.email());
373 } catch (Exception e) {
376 "Failure to obtain User " + user + " for "
382 if (summary == null) {
389 sendEmail(trans, toList, ccList,
390 "AAF Approval Notification "
391 + (system.length() == 0 ? "" : "(ENV: "
395 + "System for Fine-Grained Authorizations. You are being asked to Approve"
396 + (system.length() == 0 ? "" : " in the "
397 + system + " environment")
398 + " before AAF Actions can be taken.\n\n"
399 + "Please follow this link: \n\n\t" + url
400 + "\n\n" + summary, urgent);
401 } catch (Exception e) {
403 trans.error().log(e, "Failure to send Email");
404 return Response.ERR_NotificationFailure;
407 case PasswordExpiration:
412 "AAF Password Expiration Warning "
413 + (system.length() == 0 ? "" : "(ENV: "
417 + " System for Authorizations.\n\nOne or more passwords will expire soon or have expired"
418 + (system.length() == 0 ? "" : " in the "
419 + system + " environment")
420 + ".\n\nPasswords expired for more than 30 days without action are subject to deletion.\n\n"
421 + "Please follow each link to add a New Password with Expiration Date. Either are valid until expiration. "
422 + "Use this time to change the passwords on your system. If issues, reply to this email.\n\n"
424 } catch (Exception e) {
425 trans.error().log(e, "Failure to send Email");
426 return Response.ERR_NotificationFailure;
436 "AAF Role Expiration Warning "
437 + (system.length() == 0 ? "" : "(ENV: "
441 + " System for Authorizations. One or more roles will expire soon"
442 + (system.length() == 0 ? "" : " in the "
443 + system + " environment")
444 + ".\n\nRoles expired for more than 30 days are subject to deletion."
445 + "Please follow this link the GUI Command line, and either 'extend' or 'del' the user in the role.\n"
446 + "If issues, reply to this email.\n\n\t" + url
447 + "\n\n" + summary, urgent);
448 } catch (Exception e) {
449 trans.error().log(e, "Failure to send Email");
450 return Response.ERR_NotificationFailure;
454 return Response.ERR_NotImplemented;
461 * Default Policy is to set to 6 Months for Notification Types.
462 * add others/change as required
465 public Date whenToValidate(Notify type, Date lastValidated) {
468 case PasswordExpiration:
471 GregorianCalendar gc = new GregorianCalendar();
472 gc.setTime(lastValidated);
473 gc.add(GregorianCalendar.MONTH, 6); // 6 month policy
479 public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
480 GregorianCalendar now = new GregorianCalendar();
481 GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
484 // Extending Password give 5 extra days, max 8 days from now
485 rv.add(GregorianCalendar.DATE, 5);
486 now.add(GregorianCalendar.DATE, 8);
492 // Future requests last 15 days.
493 now.add(GregorianCalendar.DATE, 15);
497 // Passwords expire in 90 days
498 now.add(GregorianCalendar.DATE, 90);
502 // Temporary Passwords last for 12 hours.
503 now.add(GregorianCalendar.DATE, 90);
507 // Delegations expire max in 2 months, renewable to 3
508 rv.add(GregorianCalendar.MONTH, 2);
509 now.add(GregorianCalendar.MONTH, 3);
515 // Roles expire in 6 months
516 now.add(GregorianCalendar.MONTH, 6);
519 case RevokedGracePeriodEnds:
520 now.add(GregorianCalendar.DATE, 3);
524 // Unless other wise set, 6 months is default
525 now.add(GregorianCalendar.MONTH, 6);
533 public EmailWarnings emailWarningPolicy() {
534 return emailWarnings;
538 * Assume the Supervisor is the Approver.
541 public List<Identity> getApprovers(AuthzTrans trans, String user) throws OrganizationException {
542 Identity orgIdentity = getIdentity(trans, user);
543 List<Identity> orgIdentitys = new ArrayList<>();
544 if (orgIdentity!=null) {
545 Identity supervisor = orgIdentity.responsibleTo();
546 if (supervisor!=null) {
547 orgIdentitys.add(supervisor);
554 public String getApproverType() {
559 public int startOfDay() {
560 // TODO Auto-generated method stub
565 public boolean canHaveMultipleCreds(String id) {
566 // External entities are likely mono-password... if you change it, it is a global change.
567 // This is great for people, but horrible for Applications.
569 // AAF's Password can have multiple Passwords, each with their own Expiration Date.
570 // For Default Org, we'll assume true for all, but when you add your external
571 // Identity stores, you need to return "false" if they cannot support multiple Passwords like AAF
576 public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
582 DefaultOrgIdentity thisID = getIdentity(trans,vars[0]);
583 if ("a".equals(thisID.identity.status)) { // MechID
584 DefaultOrgIdentity requestor = getIdentity(trans, trans.user());
585 if (requestor!=null) {
586 Identity mechid = getIdentity(trans, vars[0]);
588 Identity sponsor = mechid.responsibleTo();
589 if (sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
592 return trans.user() + " is not the Sponsor of MechID " + vars[0];
600 case CREATE_MECHID_BY_PERM_ONLY:
601 return getName() + " only allows sponsors to create MechIDs";
603 case MAY_EXTEND_CRED_EXPIRES:
604 // If parm, use it, otherwise, trans
605 user = vars.length>1?vars[1]:trans.user();
606 return executor.hasPermission(user, root_ns,"password", root_ns , "extend")
607 ?null:user + " does not have permission to extend passwords at " + getName();
610 return policy.name() + " is unsupported at " + getName();
615 public boolean isTestEnv() {
620 public void setTestMode(boolean dryRun) {
621 this.dryRun = dryRun;
624 private String extractRealm(final String r) {
626 if ((at=r.indexOf('@'))>=0) {
627 return FQI.reverseDomain(r.substring(at+1));
632 public boolean supportsRealm(final String r) {
633 if (r.endsWith(realm)) {
636 String erealm = extractRealm(r);
637 for (String sr : supportedRealms) {
638 if (erealm.startsWith(sr)) {
647 public String supportedDomain(String user) {
649 int after_at = user.indexOf('@')+1;
650 if(after_at<user.length()) {
651 String ud = FQI.reverseDomain(user);
652 if(ud.startsWith(getDomain())) {
655 for(String s : supportedRealms) {
656 if(ud.startsWith(s)) {
657 return FQI.reverseDomain(s);
666 public synchronized void addSupportedRealm(final String r) {
667 supportedRealms.add(extractRealm(r));
671 public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
672 Boolean urgent) throws OrganizationException {
674 String mailFrom = mailer.mailFrom();
675 List<String> to = new ArrayList<>();
676 for (String em : toList) {
677 if (em.indexOf('@')<0) {
678 to.add(new DefaultOrgIdentity(trans, em, this).email());
684 List<String> cc = new ArrayList<>();
686 if (!ccList.isEmpty()) {
688 for (String em : ccList) {
689 if (em.indexOf('@')<0) {
690 cc.add(new DefaultOrgIdentity(trans, em, this).email());
697 // for now, I want all emails so we can see what goes out. Remove later
698 if (!ccList.contains(mailFrom)) {
699 ccList.add(mailFrom);
703 return mailer.sendEmail(trans,dryRun?"DefaultOrg":null,to,cc,subject,body,urgent)?0:1;
710 public boolean isUserExpireExempt(String user, Date expires) {