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.util.ArrayList;
26 import java.util.Date;
27 import java.util.GregorianCalendar;
28 import java.util.HashSet;
29 import java.util.List;
31 import java.util.regex.Pattern;
33 import javax.mail.Address;
34 import javax.mail.Message;
35 import javax.mail.MessagingException;
36 import javax.mail.Session;
37 import javax.mail.Transport;
38 import javax.mail.internet.InternetAddress;
39 import javax.mail.internet.MimeMessage;
41 import org.onap.aaf.auth.env.AuthzTrans;
42 import org.onap.aaf.auth.org.EmailWarnings;
43 import org.onap.aaf.auth.org.Executor;
44 import org.onap.aaf.auth.org.Organization;
45 import org.onap.aaf.auth.org.OrganizationException;
46 import org.onap.aaf.cadi.util.FQI;
47 import org.onap.aaf.misc.env.Env;
49 public class DefaultOrg implements Organization {
50 private static final String AAF_DATA_DIR = "aaf_data_dir";
51 private static final String PROPERTY_IS_REQUIRED = " property is Required";
54 final String atDomain;
57 private final String NAME,mailHost,mailFrom;
58 private final Set<String> supportedRealms;
61 public DefaultOrg(Env env, String realm) throws OrganizationException {
64 supportedRealms=new HashSet<String>();
65 supportedRealms.add(realm);
66 domain=FQI.reverseDomain(realm);
67 atDomain = '@'+domain;
69 NAME=env.getProperty(realm + ".name","Default Organization");
70 mailHost = env.getProperty(s=(realm + ".mailHost"), null);
72 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
74 mailFrom = env.getProperty(s=(realm + ".mailFrom"), null);
76 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
79 System.getProperties().setProperty("mail.smtp.host",mailHost);
80 System.getProperties().setProperty("mail.user", mailFrom);
81 // Get the default Session object.
82 session = Session.getDefaultInstance(System.getProperties());
86 String temp=env.getProperty(defFile = (getClass().getName()+".file"));
87 File fIdentities=null;
89 temp = env.getProperty(AAF_DATA_DIR);
91 env.warn().log(defFile, " is not defined. Using default: ",temp+"/identities.dat");
92 File dir = new File(temp);
93 fIdentities=new File(dir,"identities.dat");
95 if(!fIdentities.exists()) {
96 env.warn().log("No",fIdentities.getCanonicalPath(),"exists. Creating.");
100 fIdentities.createNewFile();
104 fIdentities = new File(temp);
105 if(!fIdentities.exists()) {
106 String dataDir = env.getProperty(AAF_DATA_DIR);
108 fIdentities = new File(dataDir,temp);
113 if(fIdentities!=null && fIdentities.exists()) {
114 identities = new Identities(fIdentities);
116 if(fIdentities==null) {
117 throw new OrganizationException("No Identities");
119 throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
122 } catch (IOException e) {
123 throw new OrganizationException(e);
127 // Implement your own Delegation System
128 static final List<String> NULL_DELEGATES = new ArrayList<String>();
130 public Identities identities;
131 private boolean dryRun;
132 private Session session;
133 public enum Types {Employee, Contractor, Application, NotActive};
134 private final static Set<String> typeSet;
137 typeSet = new HashSet<String>();
138 for(Types t : Types.values()) {
139 typeSet.add(t.name());
143 private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
146 public String getName() {
151 public String getRealm() {
156 public String getDomain() {
161 public DefaultOrgIdentity getIdentity(AuthzTrans trans, String id) throws OrganizationException {
162 int at = id.indexOf('@');
163 String attt = at<0?id:id.substring(0, at);
164 return new DefaultOrgIdentity(trans,at<0?id:id.substring(0, at),this);
167 // Note: Return a null if found; return a String Message explaining why not found.
169 public String isValidID(final AuthzTrans trans, final String id) {
171 DefaultOrgIdentity u = getIdentity(trans,id);
172 return (u==null||!u.isFound())?id + "is not an Identity in " + getName():null;
173 } catch (OrganizationException e) {
174 return getName() + " could not lookup " + id + ": " + e.getLocalizedMessage();
177 // Possible ID Pattern
178 // private static final Pattern ID_PATTERN=Pattern.compile("([\\w.-]+@[\\w.-]+).{4-13}");
179 // Another one: ID_PATTERN = "(a-z[a-z0-9]{5-8}@.*).{4-13}";
182 public boolean isValidCred(final AuthzTrans trans, final String id) {
184 int at = id.indexOf('@');
187 // Use this to prevent passwords to any but THIS domain.
188 // if(!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
191 sid = id.substring(0,at);
195 // We'll validate that it exists, rather than check patterns.
197 return isValidID(trans, sid)==null;
198 // Check Pattern (if checking existing is too long)
199 // if(id.endsWith(SUFFIX) && ID_PATTERN.matcher(id).matches()) {
205 private static final String SPEC_CHARS = "!@#$%^*-+?/,:;.";
206 private static final Pattern PASS_PATTERN=Pattern.compile("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[" + SPEC_CHARS +"]).{6,20})");
208 * Attribution: from mkyong.com
210 * (?=.*\d) # must contains one digit from 0-9
211 * (?=.*[a-z]) # must contains one lowercase characters
212 * (?=.*[A-Z]) # must contains one uppercase characters
213 * (?=.*[@#$%]) # must contains one special symbols in the list SPEC_CHARS
214 * . # match anything with previous condition checking
215 * {6,20} # length at least 6 characters and maximum of 20
219 public String isValidPassword(final AuthzTrans trans, final String user, final String password, final String... prev) {
220 for(String p : prev) {
221 if(password.contains(p)) { // A more sophisticated algorithm might be better.
222 return "Password too similar to previous passwords";
225 // If you have an Organization user/Password scheme, replace the following
226 if(PASS_PATTERN.matcher(password).matches()) {
229 return "Password does not match " + NAME + " Password Standards";
232 private static final String[] rules = new String[] {
233 "Passwords must contain one digit from 0-9",
234 "Passwords must contain one lowercase character",
235 "Passwords must contain one uppercase character",
236 "Passwords must contain one special symbols in the list \""+ SPEC_CHARS + '"',
237 "Passwords must be between 6 and 20 chars in length"
241 public String[] getPasswordRules() {
246 public Set<String> getIdentityTypes() {
251 public Response notify(AuthzTrans trans, Notify type, String url, String[] identities, String[] ccs, String summary, Boolean urgent) {
252 String system = trans.getProperty("CASS_ENV", "");
254 ArrayList<String> toList = new ArrayList<String>();
256 if (identities != null) {
257 for (String user : identities) {
259 identity = getIdentity(trans, user);
260 if (identity == null) {
262 "Failure to obtain User " + user + " for "
265 toList.add(identity.email());
267 } catch (Exception e) {
270 "Failure to obtain User " + user + " for "
276 if (toList.isEmpty()) {
277 trans.error().log("No Users listed to email");
278 return Response.ERR_NotificationFailure;
281 ArrayList<String> ccList = new ArrayList<String>();
283 // If we're sending an urgent email, CC the user's supervisor
286 trans.info().log("urgent msg for: " + identities[0]);
288 List<Identity> supervisors = getApprovers(trans, identities[0]);
289 for (Identity us : supervisors) {
290 trans.info().log("supervisor: " + us.email());
291 ccList.add(us.email());
293 } catch (Exception e) {
295 "Failed to find supervisor for " + identities[0]);
300 for (String user : ccs) {
302 identity = getIdentity(trans, user);
303 ccList.add(identity.email());
304 } catch (Exception e) {
307 "Failure to obtain User " + user + " for "
313 if (summary == null) {
320 sendEmail(trans, toList, ccList,
321 "AAF Approval Notification "
322 + (system.length() == 0 ? "" : "(ENV: "
326 + "System for Fine-Grained Authorizations. You are being asked to Approve"
327 + (system.length() == 0 ? "" : " in the "
328 + system + " environment")
329 + " before AAF Actions can be taken.\n\n"
330 + "Please follow this link: \n\n\t" + url
331 + "\n\n" + summary, urgent);
332 } catch (Exception e) {
334 trans.error().log(e, "Failure to send Email");
335 return Response.ERR_NotificationFailure;
338 case PasswordExpiration:
343 "AAF Password Expiration Warning "
344 + (system.length() == 0 ? "" : "(ENV: "
348 + " System for Authorizations.\n\nOne or more passwords will expire soon or have expired"
349 + (system.length() == 0 ? "" : " in the "
350 + system + " environment")
351 + ".\n\nPasswords expired for more than 30 days without action are subject to deletion.\n\n"
352 + "Please follow each link to add a New Password with Expiration Date. Either are valid until expiration. "
353 + "Use this time to change the passwords on your system. If issues, reply to this email.\n\n"
355 } catch (Exception e) {
356 trans.error().log(e, "Failure to send Email");
357 return Response.ERR_NotificationFailure;
367 "AAF Role Expiration Warning "
368 + (system.length() == 0 ? "" : "(ENV: "
372 + " System for Authorizations. One or more roles will expire soon"
373 + (system.length() == 0 ? "" : " in the "
374 + system + " environment")
375 + ".\n\nRoles expired for more than 30 days are subject to deletion."
376 + "Please follow this link the GUI Command line, and either 'extend' or 'del' the user in the role.\n"
377 + "If issues, reply to this email.\n\n\t" + url
378 + "\n\n" + summary, urgent);
379 } catch (Exception e) {
380 trans.error().log(e, "Failure to send Email");
381 return Response.ERR_NotificationFailure;
385 return Response.ERR_NotImplemented;
391 public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
392 Boolean urgent) throws OrganizationException {
396 List<String> to = new ArrayList<String>();
397 for(String em : toList) {
398 if(em.indexOf('@')<0) {
399 to.add(new DefaultOrgIdentity(trans, em, this).email());
405 List<String> cc = new ArrayList<String>();
407 if(!ccList.isEmpty()) {
409 for(String em : ccList) {
410 if(em.indexOf('@')<0) {
411 cc.add(new DefaultOrgIdentity(trans, em, this).email());
418 // for now, I want all emails so we can see what goes out. Remove later
419 if (!ccList.contains(mailFrom)) {
420 ccList.add(mailFrom);
425 // Create a default MimeMessage object.
426 MimeMessage message = new MimeMessage(session);
428 // Set From: header field of the header.
429 message.setFrom(new InternetAddress(mailFrom));
432 // Set To: header field of the header. This is a required field
433 // and calling module should make sure that it is not null or
435 message.addRecipients(Message.RecipientType.TO,getAddresses(to));
437 // Set CC: header field of the header.
438 if ((ccList != null) && (ccList.size() > 0)) {
439 message.addRecipients(Message.RecipientType.CC,getAddresses(cc));
442 // Set Subject: header field
443 message.setSubject(subject);
446 message.addHeader("X-Priority", "1");
449 // Now set the actual message
450 message.setText(body);
453 // override recipients
454 message.addRecipients(Message.RecipientType.TO,
455 InternetAddress.parse(mailFrom));
457 // Set Subject: header field
458 message.setSubject("[TESTMODE] " + subject);
461 message.addHeader("X-Priority", "1");
464 ArrayList<String> newBody = new ArrayList<String>();
466 Address temp[] = getAddresses(to);
467 String headerString = "TO:\t" + InternetAddress.toString(temp) + "\n";
469 temp = getAddresses(cc);
470 headerString += "CC:\t" + InternetAddress.toString(temp) + "\n";
472 newBody.add(headerString);
474 newBody.add("Text: \n");
477 String outString = "";
478 for (String s : newBody) {
479 outString += s + "\n";
482 message.setText(outString);
485 Transport.send(message);
488 } catch (MessagingException mex) {
489 System.out.println("Error messaging: "+ mex.getMessage());
490 System.out.println("Error messaging: "+ mex.toString());
491 throw new OrganizationException("Exception send email message "
499 * Default Policy is to set to 6 Months for Notification Types.
500 * add others/change as required
503 public Date whenToValidate(Notify type, Date lastValidated) {
506 case PasswordExpiration:
509 GregorianCalendar gc = new GregorianCalendar();
510 gc.setTime(lastValidated);
511 gc.add(GregorianCalendar.MONTH, 6); // 6 month policy
517 public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
518 GregorianCalendar now = new GregorianCalendar();
519 GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
522 // Extending Password give 5 extra days, max 8 days from now
523 rv.add(GregorianCalendar.DATE, 5);
524 now.add(GregorianCalendar.DATE, 8);
530 // Future requests last 15 days.
531 now.add(GregorianCalendar.DATE, 15);
535 // Passwords expire in 90 days
536 now.add(GregorianCalendar.DATE, 90);
540 // Temporary Passwords last for 12 hours.
541 now.add(GregorianCalendar.DATE, 90);
545 // Delegations expire max in 2 months, renewable to 3
546 rv.add(GregorianCalendar.MONTH, 2);
547 now.add(GregorianCalendar.MONTH, 3);
553 // Roles expire in 6 months
554 now.add(GregorianCalendar.MONTH, 6);
558 // Unless other wise set, 6 months is default
559 now.add(GregorianCalendar.MONTH, 6);
567 public EmailWarnings emailWarningPolicy() {
568 return emailWarnings;
572 * Assume the Supervisor is the Approver.
575 public List<Identity> getApprovers(AuthzTrans trans, String user) throws OrganizationException {
576 Identity orgIdentity = getIdentity(trans, user);
577 List<Identity> orgIdentitys = new ArrayList<Identity>();
578 if(orgIdentity!=null) {
579 Identity supervisor = orgIdentity.responsibleTo();
580 if(supervisor!=null) {
581 orgIdentitys.add(supervisor);
588 public String getApproverType() {
593 public int startOfDay() {
594 // TODO Auto-generated method stub
599 public boolean canHaveMultipleCreds(String id) {
600 // External entities are likely mono-password... if you change it, it is a global change.
601 // This is great for people, but horrible for Applications.
603 // AAF's Password can have multiple Passwords, each with their own Expiration Date.
604 // For Default Org, we'll assume true for all, but when you add your external
605 // Identity stores, you need to return "false" if they cannot support multiple Passwords like AAF
610 public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
615 DefaultOrgIdentity thisID = getIdentity(trans,vars[0]);
616 if("a".equals(thisID.identity.status)) { // MechID
617 DefaultOrgIdentity requestor = getIdentity(trans, trans.user());
618 if(requestor!=null) {
619 Identity mechid = getIdentity(trans, vars[0]);
621 Identity sponsor = mechid.responsibleTo();
622 if(sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
625 return trans.user() + " is not the Sponsor of MechID " + vars[0];
633 case CREATE_MECHID_BY_PERM_ONLY:
634 return getName() + " only allows sponsors to create MechIDs";
637 return policy.name() + " is unsupported at " + getName();
642 public boolean isTestEnv() {
647 public void setTestMode(boolean dryRun) {
648 this.dryRun = dryRun;
652 * Convert the delimiter String into Internet addresses with the default
657 private Address[] getAddresses(List<String> strAddress) throws OrganizationException {
658 return this.getAddresses(strAddress,";");
661 * Convert the delimiter String into Internet addresses with the
662 * delimiter of provided
663 * @param strAddresses
667 private Address[] getAddresses(List<String> strAddresses, String delimiter) throws OrganizationException {
668 Address[] addressArray = new Address[strAddresses.size()];
670 for (String addr : strAddresses)
673 addressArray[count] = new InternetAddress(addr);
676 throw new OrganizationException("Failed to parse the email address "+ addr +": "+e.getMessage());
682 private String extractRealm(final String r) {
684 if((at=r.indexOf('@'))>=0) {
685 return FQI.reverseDomain(r.substring(at+1));
690 public boolean supportsRealm(final String r) {
691 if(r.endsWith(realm)) {
694 String erealm = extractRealm(r);
695 for(String sr : supportedRealms) {
696 if(erealm.startsWith(sr)) {
705 public synchronized void addSupportedRealm(final String r) {
706 supportedRealms.add(extractRealm(r));