private void makePatternMap(List<String> patternPairs) throws IllegalArgumentException { if (patternPairs != null) { patternMap = new LinkedMap(); for (String pair : patternPairs) { // Find the last occurrence of comma to avoid regexp quoting int pos = pair.lastIndexOf(','); if (pos < 0) { throw new IllegalArgumentException("Marformed pattern,int pair; no comma: " + pair); } String regexp = pair.substring(0, pos); String pristr = pair.substring(pos + 1); int pri; Pattern pat; try { pri = Integer.parseInt(pristr); int flags = Perl5Compiler.READ_ONLY_MASK; pat = RegexpUtil.getCompiler().compile(regexp, flags); patternMap.put(pat, pri); } catch (NumberFormatException e) { throw new IllegalArgumentException("Illegal priority: " + pristr); } catch (MalformedPatternException e) { throw new IllegalArgumentException("Illegal regexp: " + regexp); } } } }
boolean hasRequiredCharacterMix(String str) { // Must have at least 3 of: upper alpha, lower alpha, numeric, special int cnt = 0; Perl5Matcher matcher = RegexpUtil.getMatcher(); for (Pattern pat : charPats) { if (matcher.contains(str, pat)) { cnt++; } } return cnt >= 3; }
/** * Return the value associated with the first pattern that the string matches, or the specified * default value if none, considering only patterns whose associated value is less than or equal * to maxPri. */ public int getMatch(String str, int dfault, int maxPri) { Perl5Matcher matcher = RegexpUtil.getMatcher(); for (Map.Entry<Pattern, Integer> ent : patternMap.entrySet()) { if (ent.getValue() <= maxPri) { Pattern pat = ent.getKey(); if (matcher.contains(str, pat)) { log.debug2("getMatch(" + str + "): " + ent.getValue()); return ent.getValue(); } } } log.debug2("getMatch(" + str + "): default: " + dfault); return dfault; }
/** Parse a string of the form [x1,y1},[x2,y2},...,[xN,yN} into a list of Points ([int,int]) */ public static List<Point> parseString(String str) throws NumberFormatException { if (StringUtil.isNullString(str)) { throw new IllegalArgumentException("Must supply non-empty string"); } ArrayList<Point> res = new ArrayList<Point>(); Perl5Matcher matcher = RegexpUtil.getMatcher(); while (matcher.contains(str, ONE_POINT_PAT)) { MatchResult matchResult = matcher.getMatch(); String xstr = matchResult.group(1); String ystr = matchResult.group(2); str = matchResult.group(3); try { int x = Integer.parseInt(xstr); int y = Integer.parseInt(ystr); res.add(new Point(x, y)); } catch (NumberFormatException e) { throw new IllegalArgumentException("bad point [" + xstr + "," + ystr + "] in " + str); } } res.trimToSize(); return res; }
/** A simple integer step function. Should be genericized */ public class IntStepFunction { static class Point { final int x; final int y; Point(int x, int y) { this.x = x; this.y = y; } public boolean equals(Object o) { if (!(o instanceof Point)) { return false; } Point op = (Point) o; return x == op.x && y == op.y; } public int hashCode() { return (int) ((x ^ (x >>> 32)) + (y ^ (y >>> 32))); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("("); if (x == Integer.MIN_VALUE) { sb.append("-INF"); } else if (x == Integer.MAX_VALUE) { sb.append("+INF"); } else { sb.append(Integer.toString(x)); } sb.append(","); sb.append(y); sb.append(")"); return sb.toString(); } } private Point points[]; /** * Create a IntStepFunction from a string specifying a list of pairs: [x1,y1},[x2,y2},...,[xN,yN} */ public IntStepFunction(String spec) { this(parseString(spec)); } /** Create a IntStepFunction from a List of Points */ public IntStepFunction(List<Point> pointList) { if (pointList == null || pointList.isEmpty()) { throw new IllegalArgumentException("Must supply non-empty pointList"); } points = new Point[pointList.size()]; int ix = 0; for (Point p : pointList) { points[ix++] = p; } } /** Return the value of the function of X */ public int getValue(int x) { if (x < points[0].x) { return 0; } Point p2 = points[0]; for (int ix = 0; ix < points.length - 1; ix++) { Point p1 = p2; p2 = points[ix + 1]; if (x < p2.x) { return p1.y; } } return p2.y; } // * Pattern picks off first x,y pair into group(1) and group(2) and rest // * of string into group(3) */ static Pattern ONE_POINT_PAT = RegexpUtil.uncheckedCompile( "^\\s*,?\\s*\\[(\\d+)\\s*,\\s*(-?\\d+)\\](.*)$", Perl5Compiler.READ_ONLY_MASK); /** Parse a string of the form [x1,y1},[x2,y2},...,[xN,yN} into a list of Points ([int,int]) */ public static List<Point> parseString(String str) throws NumberFormatException { if (StringUtil.isNullString(str)) { throw new IllegalArgumentException("Must supply non-empty string"); } ArrayList<Point> res = new ArrayList<Point>(); Perl5Matcher matcher = RegexpUtil.getMatcher(); while (matcher.contains(str, ONE_POINT_PAT)) { MatchResult matchResult = matcher.getMatch(); String xstr = matchResult.group(1); String ystr = matchResult.group(2); str = matchResult.group(3); try { int x = Integer.parseInt(xstr); int y = Integer.parseInt(ystr); res.add(new Point(x, y)); } catch (NumberFormatException e) { throw new IllegalArgumentException("bad point [" + xstr + "," + ystr + "] in " + str); } } res.trimToSize(); return res; } public String toString() { return "[CLS " + "[" + StringUtil.separatedString(points, ", ") + "]]"; } }
/** * Library of Congress password rules: * * <p>- The system must enforce a minimum password length of 8 characters for user passwords. * * <p>- The system must enforce at least three (3) of the following password complexity rules for * user passwords: (a) at least 1 upper case alphabetic character, (b) at least 1 lower case * alphabetic character, (c) at least 1 numeric character, (d) at least 1 special character. * * <p>- The system must prevent passwords with consecutive repeated characters for user accounts. * * <p>- The system must prevent the reuse of the 11 most recently used passwords for a particular * user account for user accounts. * * <p>- The system must prevent the user from changing his or her password more than one time per * day. * * <p>- The system must ensure that user passwords expire if not changed every sixty (60) days with * the exception of Public E-Authentication Level 1 or 2 systems, per NIST SP 800-63, which ensure * that user passwords expire if not changed annually. * * <p>- The system must provide a warning message seven (7) days before the user password expires */ public class LCUserAccount extends UserAccount { static final long MIN_PASSWORD_CHANGE_INTERVAL = 1 * Constants.DAY; static final long MAX_PASSWORD_CHANGE_INTERVAL = 60 * Constants.DAY; static final long PASSWORD_CHANGE_REMINDER_INTERVAL = 7 * Constants.DAY; static final long INACTIVITY_LOGOUT = 15 * Constants.MINUTE; static final int HISTORY_SIZE = 11; static final int MAX_FAILED_ATTEMPTS = 3; static final long MAX_FAILED_ATTEMPT_WINDOW = 15 * Constants.MINUTE; static final long MAX_FAILED_ATTEMPT_RESET_INTERVAL = 15 * Constants.MINUTE; static final String HASH_ALGORITHM = "SHA-256"; public LCUserAccount(String name) { super(name); } public String getType() { return "LC"; } protected int getHistorySize() { return HISTORY_SIZE; } protected String getDefaultHashAlgorithm() { return HASH_ALGORITHM; } public long getInactivityLogout() { return INACTIVITY_LOGOUT; } protected int getMinPasswordLength() { return 8; } protected long getMinPasswordChangeInterval() { return MIN_PASSWORD_CHANGE_INTERVAL; } protected long getMaxPasswordChangeInterval() { return MAX_PASSWORD_CHANGE_INTERVAL; } protected long getPasswordChangeReminderInterval() { return PASSWORD_CHANGE_REMINDER_INTERVAL; } protected int getMaxFailedAttempts() { return MAX_FAILED_ATTEMPTS; } protected long getFailedAttemptWindow() { return MAX_FAILED_ATTEMPT_WINDOW; } protected long getFailedAttemptResetInterval() { return MAX_FAILED_ATTEMPT_RESET_INTERVAL; } /** Return the hash algorithm to be used for new accounts */ @Override protected void checkLegalPassword(String newPwd, String hash, boolean isAdmin) throws IllegalPasswordChange { super.checkLegalPassword(newPwd, hash, isAdmin); if (StringUtil.hasRepeatedChar(newPwd)) { throw new IllegalPassword("Password may not contain consecutive repeated characters"); } if (!hasRequiredCharacterMix(newPwd)) { throw new IllegalPassword( "Password must contain mix of upper, lower, numeric and special characters"); } } static Pattern[] charPats = { RegexpUtil.uncheckedCompile("[a-z]", Perl5Compiler.READ_ONLY_MASK), RegexpUtil.uncheckedCompile("[A-Z]", Perl5Compiler.READ_ONLY_MASK), RegexpUtil.uncheckedCompile("[0-9]", Perl5Compiler.READ_ONLY_MASK), RegexpUtil.uncheckedCompile("[" + SPECIAL_CHARS + "]", Perl5Compiler.READ_ONLY_MASK) }; boolean hasRequiredCharacterMix(String str) { // Must have at least 3 of: upper alpha, lower alpha, numeric, special int cnt = 0; Perl5Matcher matcher = RegexpUtil.getMatcher(); for (Pattern pat : charPats) { if (matcher.contains(str, pat)) { cnt++; } } return cnt >= 3; } public static class Factory extends UserAccount.Factory { public UserAccount newUser(String name, AccountManager acctMgr, Configuration config) { UserAccount acct = new LCUserAccount(name); acct.init(acctMgr, config); return acct; } } }