1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.server.core.kerberos;
21
22
23 import org.apache.directory.server.core.entry.ServerAttribute;
24 import org.apache.directory.server.core.entry.ServerBinaryValue;
25 import org.apache.directory.server.core.entry.ServerEntry;
26 import org.apache.directory.server.core.entry.ServerStringValue;
27 import org.apache.directory.server.core.interceptor.BaseInterceptor;
28 import org.apache.directory.server.core.interceptor.Interceptor;
29 import org.apache.directory.server.core.interceptor.NextInterceptor;
30 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
31 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
32 import org.apache.directory.shared.ldap.constants.SchemaConstants;
33 import org.apache.directory.shared.ldap.entry.Modification;
34 import org.apache.directory.shared.ldap.entry.Value;
35 import org.apache.directory.shared.ldap.name.LdapDN;
36 import org.apache.directory.shared.ldap.util.StringTools;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import java.util.ArrayList;
41 import java.util.List;
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class PasswordPolicyInterceptor extends BaseInterceptor
56 {
57
58 private static final Logger log = LoggerFactory.getLogger( PasswordPolicyInterceptor.class );
59
60
61 public static final String NAME = "passwordPolicyService";
62
63
64
65
66
67
68 public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
69 {
70 LdapDN normName = addContext.getDn();
71
72 ServerEntry entry = addContext.getEntry();
73
74 log.debug( "Adding the entry '{}' for DN '{}'.", entry, normName.getUpName() );
75
76 if ( entry.get( SchemaConstants.USER_PASSWORD_AT ) != null )
77 {
78 String username = null;
79
80 ServerBinaryValue userPassword = (ServerBinaryValue)entry.get( SchemaConstants.USER_PASSWORD_AT ).get();
81
82
83 String strUserPassword = StringTools.utf8ToString( userPassword.get() );
84
85 if ( log.isDebugEnabled() )
86 {
87 StringBuffer sb = new StringBuffer();
88 sb.append( "'" + strUserPassword + "' ( " );
89 sb.append( userPassword );
90 sb.append( " )" );
91 log.debug( "Adding Attribute id : 'userPassword', Values : [ {} ]", sb.toString() );
92 }
93
94 if ( entry.get( SchemaConstants.CN_AT ) != null )
95 {
96 ServerStringValue attr = (ServerStringValue)entry.get( SchemaConstants.CN_AT ).get();
97 username = attr.get();
98 }
99
100
101 check( username, strUserPassword );
102 }
103
104 next.add( addContext );
105 }
106
107
108
109
110
111
112 public void modify( NextInterceptor next, ModifyOperationContext modContext ) throws Exception
113 {
114 LdapDN name = modContext.getDn();
115
116 List<Modification> mods = modContext.getModItems();
117
118 String operation = null;
119
120 for ( Modification mod:mods )
121 {
122 if ( log.isDebugEnabled() )
123 {
124 switch ( mod.getOperation() )
125 {
126 case ADD_ATTRIBUTE:
127 operation = "Adding";
128 break;
129
130 case REMOVE_ATTRIBUTE:
131 operation = "Removing";
132 break;
133
134 case REPLACE_ATTRIBUTE:
135 operation = "Replacing";
136 break;
137 }
138 }
139
140 ServerAttribute attr = (ServerAttribute)mod.getAttribute();
141
142 if ( attr.instanceOf( SchemaConstants.USER_PASSWORD_AT ) )
143 {
144 Value<?> userPassword = attr.get();
145 String pwd = "";
146
147 if ( userPassword != null )
148 {
149 if ( userPassword instanceof ServerStringValue )
150 {
151 log.debug( "{} Attribute id : 'userPassword', Values : [ '{}' ]", operation, attr );
152 pwd = ((ServerStringValue)userPassword).get();
153 }
154 else if ( userPassword instanceof ServerBinaryValue )
155 {
156 ServerBinaryValue password = (ServerBinaryValue)userPassword.get();
157
158 String string = "";
159
160 if ( password != null )
161 {
162 string = StringTools.utf8ToString( password.get() );
163 }
164
165 if ( log.isDebugEnabled() )
166 {
167 StringBuffer sb = new StringBuffer();
168 sb.append( "'" + string + "' ( " );
169 sb.append( StringTools.dumpBytes( password.get() ).trim() );
170 sb.append( " )" );
171 log.debug( "{} Attribute id : 'userPassword', Values : [ {} ]", operation, sb.toString() );
172 }
173
174 pwd = string;
175 }
176
177
178 check( name.getUpName(), pwd );
179 }
180 }
181
182 if ( log.isDebugEnabled() )
183 {
184 log.debug( operation + " for entry '" + name.getUpName() + "' the attribute " + mod.getAttribute() );
185 }
186 }
187
188 next.modify( modContext );
189 }
190
191
192 void check( String username, String password ) throws Exception
193 {
194 int passwordLength = 6;
195 int categoryCount = 2;
196 int tokenSize = 3;
197
198 if ( !isValid( username, password, passwordLength, categoryCount, tokenSize ) )
199 {
200 String explanation = buildErrorMessage( username, password, passwordLength, categoryCount, tokenSize );
201 log.error( explanation );
202
203 throw new Exception( explanation );
204 }
205 }
206
207
208
209
210
211
212
213
214 boolean isValid( String username, String password, int passwordLength, int categoryCount, int tokenSize )
215 {
216 return isValidPasswordLength( password, passwordLength ) && isValidCategoryCount( password, categoryCount )
217 && isValidUsernameSubstring( username, password, tokenSize );
218 }
219
220
221
222
223
224 boolean isValidPasswordLength( String password, int passwordLength )
225 {
226 return password.length() >= passwordLength;
227 }
228
229
230
231
232
233
234
235
236
237 boolean isValidCategoryCount( String password, int categoryCount )
238 {
239 int uppercase = 0;
240 int lowercase = 0;
241 int digit = 0;
242 int nonAlphaNumeric = 0;
243
244 char[] characters = password.toCharArray();
245
246 for ( char character:characters )
247 {
248 if ( Character.isLowerCase( character ) )
249 {
250 lowercase = 1;
251 }
252 else
253 {
254 if ( Character.isUpperCase( character ) )
255 {
256 uppercase = 1;
257 }
258 else
259 {
260 if ( Character.isDigit( character ) )
261 {
262 digit = 1;
263 }
264 else
265 {
266 if ( !Character.isLetterOrDigit( character ) )
267 {
268 nonAlphaNumeric = 1;
269 }
270 }
271 }
272 }
273 }
274
275 return ( uppercase + lowercase + digit + nonAlphaNumeric ) >= categoryCount;
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 boolean isValidUsernameSubstring( String username, String password, int tokenSize )
292 {
293 String[] tokens = username.split( "[^a-zA-Z]" );
294
295 for ( int ii = 0; ii < tokens.length; ii++ )
296 {
297 if ( tokens[ii].length() >= tokenSize )
298 {
299 if ( password.matches( "(?i).*" + tokens[ii] + ".*" ) )
300 {
301 return false;
302 }
303 }
304 }
305
306 return true;
307 }
308
309
310 private String buildErrorMessage( String username, String password, int passwordLength, int categoryCount,
311 int tokenSize )
312 {
313 List<String> violations = new ArrayList<String>();
314
315 if ( !isValidPasswordLength( password, passwordLength ) )
316 {
317 violations.add( "length too short" );
318 }
319
320 if ( !isValidCategoryCount( password, categoryCount ) )
321 {
322 violations.add( "insufficient character mix" );
323 }
324
325 if ( !isValidUsernameSubstring( username, password, tokenSize ) )
326 {
327 violations.add( "contains portions of username" );
328 }
329
330 StringBuffer sb = new StringBuffer( "Password violates policy: " );
331
332 boolean isFirst = true;
333
334 for ( String violation : violations )
335 {
336 if ( isFirst )
337 {
338 isFirst = false;
339 }
340 else
341 {
342 sb.append( ", " );
343 }
344
345 sb.append( violation );
346 }
347
348 return sb.toString();
349 }
350 }