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 javax.mail.Address;
35 import javax.mail.Message;
36 import javax.mail.MessagingException;
37 import javax.mail.Session;
38 import javax.mail.Transport;
39 import javax.mail.internet.InternetAddress;
40 import javax.mail.internet.MimeMessage;
42 import org.onap.aaf.auth.env.AuthzTrans;
43 import org.onap.aaf.auth.org.EmailWarnings;
44 import org.onap.aaf.auth.org.Executor;
45 import org.onap.aaf.auth.org.Organization;
46 import org.onap.aaf.auth.org.OrganizationException;
47 import org.onap.aaf.cadi.util.FQI;
48 import org.onap.aaf.misc.env.Env;
50 public class DefaultOrg implements Organization {
51 private static final String AAF_DATA_DIR = "aaf_data_dir";
52 private static final String PROPERTY_IS_REQUIRED = " property is Required";
55 final String atDomain;
58 private final String NAME,mailHost,mailFrom;
59 private final Set<String> supportedRealms;
61 public DefaultOrg(Env env, String realm) throws OrganizationException {
63 supportedRealms=new HashSet<String>();
64 supportedRealms.add(realm);
65 domain=FQI.reverseDomain(realm);
66 atDomain = '@'+domain;
68 NAME=env.getProperty(realm + ".name","Default Organization");
69 mailHost = env.getProperty(s=(realm + ".mailHost"), null);
71 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
73 mailFrom = env.getProperty(s=(realm + ".mailFrom"), null);
75 throw new OrganizationException(s + PROPERTY_IS_REQUIRED);
78 System.getProperties().setProperty("mail.smtp.host",mailHost);
79 System.getProperties().setProperty("mail.user", mailFrom);
80 // Get the default Session object.
81 session = Session.getDefaultInstance(System.getProperties());
85 String temp=env.getProperty(defFile = (getClass().getName()+".file"));
86 File fIdentities=null;
88 temp = env.getProperty(AAF_DATA_DIR);
90 env.warn().log(defFile, " is not defined. Using default: ",temp+"/identities.dat");
91 File dir = new File(temp);
92 fIdentities=new File(dir,"identities.dat");
93 if(!fIdentities.exists()) {
94 env.warn().log("No",fIdentities.getCanonicalPath(),"exists. Creating.");
98 fIdentities.createNewFile();
102 fIdentities = new File(temp);
103 if(!fIdentities.exists()) {
104 String dataDir = env.getProperty(AAF_DATA_DIR);
106 fIdentities = new File(dataDir,temp);
111 if(fIdentities!=null && fIdentities.exists()) {
112 identities = new Identities(fIdentities);
114 if(fIdentities==null) {
115 throw new OrganizationException("No Identities");
117 throw new OrganizationException(fIdentities.getCanonicalPath() + " does not exist.");
120 } catch (IOException e) {
121 throw new OrganizationException(e);
125 // Implement your own Delegation System
126 static final List<String> NULL_DELEGATES = new ArrayList<String>();
128 public Identities identities;
129 private boolean dryRun;
130 private Session session;
131 public enum Types {Employee, Contractor, Application, NotActive};
132 private final static Set<String> typeSet;
135 typeSet = new HashSet<String>();
136 for(Types t : Types.values()) {
137 typeSet.add(t.name());
141 private static final EmailWarnings emailWarnings = new DefaultOrgWarnings();
144 public String getName() {
149 public String getRealm() {
154 public String getDomain() {
159 public DefaultOrgIdentity getIdentity(AuthzTrans trans, String id) throws OrganizationException {
160 int at = id.indexOf('@');
161 return new DefaultOrgIdentity(trans,at<0?id:id.substring(0, at),this);
164 // Note: Return a null if found; return a String Message explaining why not found.
166 public String isValidID(final AuthzTrans trans, final String id) {
168 DefaultOrgIdentity u = getIdentity(trans,id);
169 return (u==null||!u.isFound())?id + "is not an Identity in " + getName():null;
170 } catch (OrganizationException e) {
171 return getName() + " could not lookup " + id + ": " + e.getLocalizedMessage();
174 // Possible ID Pattern
175 // private static final Pattern ID_PATTERN=Pattern.compile("([\\w.-]+@[\\w.-]+).{4-13}");
176 // Another one: ID_PATTERN = "(a-z[a-z0-9]{5-8}@.*).{4-13}";
179 public boolean isValidCred(final AuthzTrans trans, final String id) {
181 int at = id.indexOf('@');
184 // Use this to prevent passwords to any but THIS domain.
185 // if(!id.regionMatches(at+1, domain, 0, id.length()-at-1)) {
188 sid = id.substring(0,at);
192 // We'll validate that it exists, rather than check patterns.
194 return isValidID(trans, sid)==null;
195 // Check Pattern (if checking existing is too long)
196 // if(id.endsWith(SUFFIX) && ID_PATTERN.matcher(id).matches()) {
202 private static final String SPEC_CHARS = "!@#$%^*-+?/,:;.";
203 private static final Pattern PASS_PATTERN=Pattern.compile("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[" + SPEC_CHARS +"]).{6,20})");
205 * Attribution: from mkyong.com
207 * (?=.*\d) # must contains one digit from 0-9
208 * (?=.*[a-z]) # must contains one lowercase characters
209 * (?=.*[A-Z]) # must contains one uppercase characters
210 * (?=.*[@#$%]) # must contains one special symbols in the list SPEC_CHARS
211 * . # match anything with previous condition checking
212 * {6,20} # length at least 6 characters and maximum of 20
216 public String isValidPassword(final AuthzTrans trans, final String user, final String password, final String... prev) {
217 for(String p : prev) {
218 if(password.contains(p)) { // A more sophisticated algorithm might be better.
219 return "Password too similar to previous passwords";
222 // If you have an Organization user/Password scheme, replace the following
223 if(PASS_PATTERN.matcher(password).matches()) {
226 return "Password does not match " + NAME + " Password Standards";
229 private static final String[] rules = new String[] {
230 "Passwords must contain one digit from 0-9",
231 "Passwords must contain one lowercase character",
232 "Passwords must contain one uppercase character",
233 "Passwords must contain one special symbols in the list \""+ SPEC_CHARS + '"',
234 "Passwords must be between 6 and 20 chars in length"
238 public String[] getPasswordRules() {
243 public Set<String> getIdentityTypes() {
248 public Response notify(AuthzTrans trans, Notify type, String url, String[] identities, String[] ccs, String summary, Boolean urgent) {
249 String system = trans.getProperty("CASS_ENV", "");
251 ArrayList<String> toList = new ArrayList<String>();
253 if (identities != null) {
254 for (String user : identities) {
256 identity = getIdentity(trans, user);
257 if (identity == null) {
259 "Failure to obtain User " + user + " for "
262 toList.add(identity.email());
264 } catch (Exception e) {
267 "Failure to obtain User " + user + " for "
273 if (toList.isEmpty()) {
274 trans.error().log("No Users listed to email");
275 return Response.ERR_NotificationFailure;
278 ArrayList<String> ccList = new ArrayList<String>();
280 // If we're sending an urgent email, CC the user's supervisor
283 trans.info().log("urgent msg for: " + identities[0]);
285 List<Identity> supervisors = getApprovers(trans, identities[0]);
286 for (Identity us : supervisors) {
287 trans.info().log("supervisor: " + us.email());
288 ccList.add(us.email());
290 } catch (Exception e) {
292 "Failed to find supervisor for " + identities[0]);
297 for (String user : ccs) {
299 identity = getIdentity(trans, user);
300 ccList.add(identity.email());
301 } catch (Exception e) {
304 "Failure to obtain User " + user + " for "
310 if (summary == null) {
317 sendEmail(trans, toList, ccList,
318 "AAF Approval Notification "
319 + (system.length() == 0 ? "" : "(ENV: "
323 + "System for Fine-Grained Authorizations. You are being asked to Approve"
324 + (system.length() == 0 ? "" : " in the "
325 + system + " environment")
326 + " before AAF Actions can be taken.\n\n"
327 + "Please follow this link: \n\n\t" + url
328 + "\n\n" + summary, urgent);
329 } catch (Exception e) {
330 trans.error().log(e, "Failure to send Email");
331 return Response.ERR_NotificationFailure;
334 case PasswordExpiration:
339 "AAF Password Expiration Warning "
340 + (system.length() == 0 ? "" : "(ENV: "
344 + " System for Authorizations.\n\nOne or more passwords will expire soon or have expired"
345 + (system.length() == 0 ? "" : " in the "
346 + system + " environment")
347 + ".\n\nPasswords expired for more than 30 days without action are subject to deletion.\n\n"
348 + "Please follow each link to add a New Password with Expiration Date. Either are valid until expiration. "
349 + "Use this time to change the passwords on your system. If issues, reply to this email.\n\n"
351 } catch (Exception e) {
352 trans.error().log(e, "Failure to send Email");
353 return Response.ERR_NotificationFailure;
363 "AAF Role Expiration Warning "
364 + (system.length() == 0 ? "" : "(ENV: "
368 + " System for Authorizations. One or more roles will expire soon"
369 + (system.length() == 0 ? "" : " in the "
370 + system + " environment")
371 + ".\n\nRoles expired for more than 30 days are subject to deletion."
372 + "Please follow this link the GUI Command line, and either 'extend' or 'del' the user in the role.\n"
373 + "If issues, reply to this email.\n\n\t" + url
374 + "\n\n" + summary, urgent);
375 } catch (Exception e) {
376 trans.error().log(e, "Failure to send Email");
377 return Response.ERR_NotificationFailure;
381 return Response.ERR_NotImplemented;
387 public int sendEmail(AuthzTrans trans, List<String> toList, List<String> ccList, String subject, String body,
388 Boolean urgent) throws OrganizationException {
391 List<String> to = new ArrayList<String>();
392 for(String em : toList) {
393 if(em.indexOf('@')<0) {
394 to.add(new DefaultOrgIdentity(trans, em, this).email());
400 List<String> cc = new ArrayList<String>();
402 if(!ccList.isEmpty()) {
404 for(String em : ccList) {
405 if(em.indexOf('@')<0) {
406 cc.add(new DefaultOrgIdentity(trans, em, this).email());
413 // for now, I want all emails so we can see what goes out. Remove later
414 if (!ccList.contains(mailFrom)) {
415 ccList.add(mailFrom);
420 // Create a default MimeMessage object.
421 MimeMessage message = new MimeMessage(session);
423 // Set From: header field of the header.
424 message.setFrom(new InternetAddress(mailFrom));
427 // Set To: header field of the header. This is a required field
428 // and calling module should make sure that it is not null or
430 message.addRecipients(Message.RecipientType.TO,getAddresses(to));
432 // Set CC: header field of the header.
433 if ((ccList != null) && (ccList.size() > 0)) {
434 message.addRecipients(Message.RecipientType.CC,getAddresses(cc));
437 // Set Subject: header field
438 message.setSubject(subject);
441 message.addHeader("X-Priority", "1");
444 // Now set the actual message
445 message.setText(body);
447 // override recipients
448 message.addRecipients(Message.RecipientType.TO,
449 InternetAddress.parse(mailFrom));
451 // Set Subject: header field
452 message.setSubject("[TESTMODE] " + subject);
455 message.addHeader("X-Priority", "1");
458 ArrayList<String> newBody = new ArrayList<String>();
460 Address temp[] = getAddresses(to);
461 String headerString = "TO:\t" + InternetAddress.toString(temp) + "\n";
463 temp = getAddresses(cc);
464 headerString += "CC:\t" + InternetAddress.toString(temp) + "\n";
466 newBody.add(headerString);
468 newBody.add("Text: \n");
471 String outString = "";
472 for (String s : newBody) {
473 outString += s + "\n";
476 message.setText(outString);
479 Transport.send(message);
482 } catch (MessagingException mex) {
483 throw new OrganizationException("Exception send email message "
491 * Default Policy is to set to 6 Months for Notification Types.
492 * add others/change as required
495 public Date whenToValidate(Notify type, Date lastValidated) {
498 case PasswordExpiration:
501 GregorianCalendar gc = new GregorianCalendar();
502 gc.setTime(lastValidated);
503 gc.add(GregorianCalendar.MONTH, 6); // 6 month policy
509 public GregorianCalendar expiration(GregorianCalendar gc, Expiration exp, String... extra) {
510 GregorianCalendar now = new GregorianCalendar();
511 GregorianCalendar rv = gc==null?now:(GregorianCalendar)gc.clone();
514 // Extending Password give 5 extra days, max 8 days from now
515 rv.add(GregorianCalendar.DATE, 5);
516 now.add(GregorianCalendar.DATE, 8);
522 // Future requests last 15 days.
523 now.add(GregorianCalendar.DATE, 15);
527 // Passwords expire in 90 days
528 now.add(GregorianCalendar.DATE, 90);
532 // Temporary Passwords last for 12 hours.
533 now.add(GregorianCalendar.DATE, 90);
537 // Delegations expire max in 2 months, renewable to 3
538 rv.add(GregorianCalendar.MONTH, 2);
539 now.add(GregorianCalendar.MONTH, 3);
545 // Roles expire in 6 months
546 now.add(GregorianCalendar.MONTH, 6);
550 // Unless other wise set, 6 months is default
551 now.add(GregorianCalendar.MONTH, 6);
559 public EmailWarnings emailWarningPolicy() {
560 return emailWarnings;
564 * Assume the Supervisor is the Approver.
567 public List<Identity> getApprovers(AuthzTrans trans, String user) throws OrganizationException {
568 Identity orgIdentity = getIdentity(trans, user);
569 List<Identity> orgIdentitys = new ArrayList<Identity>();
570 if(orgIdentity!=null) {
571 Identity supervisor = orgIdentity.responsibleTo();
572 if(supervisor!=null) {
573 orgIdentitys.add(supervisor);
580 public String getApproverType() {
585 public int startOfDay() {
586 // TODO Auto-generated method stub
591 public boolean canHaveMultipleCreds(String id) {
592 // External entities are likely mono-password... if you change it, it is a global change.
593 // This is great for people, but horrible for Applications.
595 // AAF's Password can have multiple Passwords, each with their own Expiration Date.
596 // For Default Org, we'll assume true for all, but when you add your external
597 // Identity stores, you need to return "false" if they cannot support multiple Passwords like AAF
602 public String validate(AuthzTrans trans, Policy policy, Executor executor, String... vars) throws OrganizationException {
607 DefaultOrgIdentity thisID = getIdentity(trans,vars[0]);
608 if("a".equals(thisID.identity.status)) { // MechID
609 DefaultOrgIdentity requestor = getIdentity(trans, trans.user());
610 if(requestor!=null) {
611 Identity mechid = getIdentity(trans, vars[0]);
613 Identity sponsor = mechid.responsibleTo();
614 if(sponsor!=null && requestor.fullID().equals(sponsor.fullID())) {
617 return trans.user() + " is not the Sponsor of MechID " + vars[0];
625 case CREATE_MECHID_BY_PERM_ONLY:
626 return getName() + " only allows sponsors to create MechIDs";
629 return policy.name() + " is unsupported at " + getName();
634 public boolean isTestEnv() {
639 public void setTestMode(boolean dryRun) {
640 this.dryRun = dryRun;
644 * Convert the delimiter String into Internet addresses with the default
649 private Address[] getAddresses(List<String> strAddress) throws OrganizationException {
650 return this.getAddresses(strAddress,";");
653 * Convert the delimiter String into Internet addresses with the
654 * delimiter of provided
659 private Address[] getAddresses(List<String> strAddresses, String delimiter) throws OrganizationException {
660 Address[] addressArray = new Address[strAddresses.size()];
662 for (String addr : strAddresses)
665 addressArray[count] = new InternetAddress(addr);
668 throw new OrganizationException("Failed to parse the email address "+ addr +": "+e.getMessage());
674 private String extractRealm(final String r) {
676 if((at=r.indexOf('@'))>=0) {
677 return FQI.reverseDomain(r.substring(at+1));
682 public boolean supportsRealm(final String r) {
683 if(r.endsWith(realm)) {
686 String erealm = extractRealm(r);
687 for(String sr : supportedRealms) {
688 if(erealm.startsWith(sr)) {
697 public synchronized void addSupportedRealm(final String r) {
698 supportedRealms.add(extractRealm(r));