crowd-stefanwille 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ class Crowd #:nodoc:
2
+ module Version #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 5
5
+ TINY = 8
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/crowd.rb ADDED
@@ -0,0 +1,669 @@
1
+ #
2
+ # Updated by Stefan Wille, post@stefanwille.com on 2010-07-13
3
+ # Updated by Evgeny Zislis, evgeny.zislis@gmail.con on 2008-05-15
4
+ # Created by Jason Rimmer, jrimmer@irth.net on 2007-10-16.
5
+ # I hereby place this work that I have authored into the public domain
6
+ # and in the process abandon all copyright protection.
7
+ #
8
+ require 'sha1'
9
+ require 'base64'
10
+ require 'rubygems'
11
+ gem 'soap4r'
12
+
13
+ require File.join(File.dirname(__FILE__), 'crowd', 'version')
14
+ require File.join(File.dirname(__FILE__), 'crowd', 'soap', 'driver.rb')
15
+
16
+ #
17
+ # Place 'server.wiredump_dev = STDERR' after any to see
18
+ # the raw SOAP calls and responses in a test console
19
+ #
20
+
21
+ # public static final String USERNAME = "username";
22
+ # public static final String FIRSTNAME = "givenName";
23
+ # public static final String LASTNAME = "sn";
24
+ # public static final String DISPLAYNAME = "displayName";
25
+ # public static final String EMAIL = "mail";
26
+ # public static final String ICON_LOCATION = "iconLocation";
27
+ # public static final String PASSWORD_LASTCHANGED = "passwordLastChanged";
28
+ # public static final String LAST_AUTHENTICATED = "lastAuthenticated";
29
+ # public static final String INVALID_PASSWORD_ATTEMPTS = "invalidPasswordAttempts";
30
+ # public static final String REQUIRES_PASSWORD_CHANGE = "requiresPasswordChange";
31
+ # public static final String ACTIVE = "active";
32
+ #
33
+
34
+ class Crowd
35
+
36
+ #
37
+ # shortcircuit Crowd::SOAP
38
+ #
39
+ include SOAP
40
+
41
+ #
42
+ # Class variables
43
+ #
44
+ private
45
+
46
+ @@application_token = nil
47
+ @@crowd_app_name = nil
48
+ @@crowd_app_pword = nil
49
+
50
+ public
51
+
52
+ @@crowd_url = nil
53
+
54
+ def self.crowd_url=(value); @@crowd_url = value; end
55
+ def self.crowd_app_name=(value); @@crowd_app_name = value; end
56
+ def self.crowd_app_pword=(value); @@crowd_app_pword = value; end
57
+
58
+
59
+ # for testing
60
+ def self.crowd_url; @@crowd_url; end
61
+ def self.crowd_app_name; @@crowd_app_name; end
62
+ def self.crowd_app_pword; @@crowd_app_pword; end
63
+ def self.application_token; @@application_token; end
64
+
65
+ public
66
+
67
+ #
68
+ # Exceptions
69
+ #
70
+ class AuthenticationException < StandardError; end
71
+ class AuthenticationConnectionException < ::Errno::ECONNREFUSED; end
72
+ class AuthenticationInvalidCredentialException < AuthenticationException; end
73
+ class AuthenticationInvalidException < AuthenticationException; end
74
+ class AuthenticationObjectNotFoundException < AuthenticationException; end
75
+
76
+ #
77
+ # Public methods
78
+ #
79
+
80
+ ##
81
+ # Authenticates an application client to the Crowd security server.
82
+ def self.authenticate_application(validation_factors = {})
83
+ pword = PasswordCredential.new(@@crowd_app_pword, false)
84
+ aovf = helper_validation_factors(validation_factors)
85
+ ctx = ApplicationAuthenticationContext.new(pword, @@crowd_app_name, aovf)
86
+ arg = AuthenticateApplication.new(ctx)
87
+ begin
88
+ response = server.authenticateApplication(arg)
89
+ rescue Errno::ECONNREFUSED => e
90
+ raise AuthenticationConnectionException, e
91
+ end
92
+ @@application_token = response.out
93
+ end
94
+
95
+ ##
96
+ # Authenticates a principal verses the calling who is in the application's assigned directory.
97
+ #
98
+ # To use SSO, set:
99
+ # validation_factors = { 'USER_AGENT' => '...', 'REMOTE_ADDRESS' => '...' }
100
+ # for proxy users { 'X_FORWARDED_FOR" => '...' } might be useful as well.
101
+ def self.authenticate_principal(username, password, validation_factors = {})
102
+ response = authenticated_connection do
103
+ pword = PasswordCredential.new(password, false)
104
+ aovf = helper_validation_factors(validation_factors)
105
+ ctx = UserAuthenticationContext.new(@@application_token.name, pword, username, aovf)
106
+ arg = AuthenticatePrincipal.new(@@application_token, ctx)
107
+
108
+ server.authenticatePrincipal(arg)
109
+ end
110
+
111
+ #evaluate the response. ideally, the authenticatePrincipal call should
112
+ #return an AuthenticationInvalidCredentialException and we can handle it more
113
+ #nicely below.
114
+ case response
115
+ when AuthenticatePrincipalResponse
116
+ return response.out
117
+ when InvalidAuthenticationException
118
+ return nil
119
+ when InactiveAccountException
120
+ return nil
121
+ when nil #no reponse
122
+ raise AuthenticationInvalidCredentialException, response
123
+ when ::AuthenticationException
124
+ raise AuthenticationInvalidCredentialException, response
125
+ else
126
+ raise AuthenticationException, response
127
+ end
128
+ end
129
+
130
+
131
+ ##
132
+ # Authenticates a principal without validating a password.
133
+ def self.create_principal_token(username, validation_factors = {})
134
+ response = authenticated_connection do
135
+ aovf = helper_validation_factors(validation_factors)
136
+ arg = CreatePrincipalToken.new(@@application_token, username, aovf)
137
+ server.createPrincipalToken(arg)
138
+ end
139
+ response.out
140
+ end
141
+
142
+ ##
143
+ # Checks if the principal's current token is still valid.
144
+ def self.is_valid_principal_token?(principal_token, validation_factors = {})
145
+ response = authenticated_connection do
146
+ aovf = ArrayOfValidationFactor.new
147
+ validation_factors.each { |name,value| aovf << ValidationFactor.new(name, value)}
148
+ arg = IsValidPrincipalToken.new(@@application_token, principal_token, aovf)
149
+ server.isValidPrincipalToken(arg)
150
+ end
151
+
152
+ case response
153
+ when IsValidPrincipalTokenResponse
154
+ return response.out
155
+ else
156
+ raise AuthenticationException, response
157
+ end
158
+ end
159
+
160
+ ##
161
+ # Add Principal
162
+ def self.add_principal(username, password, description, is_active, attributes)
163
+ response = authenticated_connection do
164
+
165
+ attrs = ArrayOfSOAPAttribute.new()
166
+ attributes.each do |key, val|
167
+ if (val.class == Array)
168
+ attrVal = ArrayOfString.new(val)
169
+ else
170
+ attrVal = ArrayOfString.new
171
+ attrVal << val
172
+ end
173
+ attrs << SOAPAttribute.new(key, attrVal)
174
+ end
175
+
176
+ principal = SOAPPrincipal.new(nil, is_active, attrs, nil, description, nil, nil, username)
177
+ pword = PasswordCredential.new(password, false)
178
+ arg = AddPrincipal.new(@@application_token, principal, pword)
179
+
180
+ server.addPrincipal(arg)
181
+ end
182
+
183
+ case response
184
+ when AddPrincipalResponse
185
+ return true
186
+ when InvalidCredentialException
187
+ raise AuthenticationInvalidCredentalException
188
+ when InvalidUserException
189
+ raise AuthenticationInvalidException, response
190
+ else
191
+ raise AuthenticationException, response
192
+ end
193
+ end
194
+
195
+ ##
196
+ # Find Principal via username
197
+ def self.find_principal_by_username(username)
198
+ response = authenticated_connection do
199
+ arg = FindPrincipalByName.new(@@application_token, username)
200
+ server.findPrincipalByName(arg)
201
+ end
202
+
203
+ case response
204
+ when FindPrincipalByNameResponse
205
+ return parse_principal(response.out)
206
+ when ObjectNotFoundException
207
+ return nil
208
+ else
209
+ raise AuthenticationException, response
210
+ end
211
+ rescue AuthenticationException => e
212
+ raise AuthenticationObjectNotFoundException, e
213
+ end
214
+
215
+ ##
216
+ # Find Principal via token
217
+ def self.find_principal_by_token(token)
218
+ response = authenticated_connection do
219
+ arg = FindPrincipalByToken.new(@@application_token, token)
220
+ server.findPrincipalByToken(arg)
221
+ end
222
+ case response
223
+ when FindPrincipalByTokenResponse
224
+ return parse_principal(response.out)
225
+ when ObjectNotFoundException
226
+ return nil
227
+ else
228
+ raise AuthenticationException, response
229
+ end
230
+ rescue AuthenticationObjectNotFoundException
231
+ return nil
232
+ rescue AuthenticationException => e
233
+ nil
234
+ rescue ::SOAP::FaultError => e
235
+ raise AuthenticationException, e.message
236
+ end
237
+
238
+ ##
239
+ # Invalidate Principal Token
240
+ def self.invalidate_principal_token(token)
241
+ response = authenticated_connection do
242
+ arg = InvalidatePrincipalToken.new(@@application_token, token)
243
+ server.invalidatePrincipalToken(arg)
244
+ end
245
+
246
+ case response
247
+ when InvalidatePrincipalTokenResponse
248
+ return true
249
+ else
250
+ raise AuthenticationException, response
251
+ end
252
+ end
253
+
254
+ ##
255
+ # Remove principal attribute
256
+ def self.remove_attribute_principal(username, attributes)
257
+ if(attributes.class != Array)
258
+ attributes = [attributes]
259
+ end
260
+
261
+ attributes.each do |attr|
262
+ response = authenticated_connection do
263
+ arg = RemoveAttributeFromPrincipal.new(@@application_token, username, attr)
264
+ server.removeAttributeFromPrincipal(arg)
265
+ end
266
+
267
+ case response
268
+ when RemoveAttributeFromPrincipalResponse
269
+ # Burying as this means it succeeded
270
+ when ObjectNotFoundException
271
+ raise AuthenticationObjectNotFoundException
272
+ else
273
+ raise AuthenticationException, response
274
+ end
275
+ end
276
+ end
277
+
278
+ ##
279
+ # Add attribute to principal
280
+ def self.add_attribute_principal(username, attributes)
281
+ attributes.each do |key, val|
282
+ response = authenticated_connection do
283
+ if(val.class == Array)
284
+ valArray = ArrayOfString.new(val)
285
+ else
286
+ valArray = ArrayOfString.new
287
+ valArray << val
288
+ end
289
+
290
+ tuple = SOAPAttribute.new(key, valArray)
291
+ arg = AddAttributeToPrincipal.new(@@application_token, username, tuple)
292
+
293
+ server.addAttributeToPrincipal(arg)
294
+ end
295
+
296
+ case response
297
+ when AddAttributeToPrincipalResponse
298
+ # Burying it because this means it was successful
299
+ when ObjectNotFoundException
300
+ raise AuthenticationObjectNotFoundException
301
+ else
302
+ raise AuthenticationException, response
303
+ end
304
+ end
305
+
306
+ true
307
+ end
308
+
309
+ ##
310
+ # Update attribute on principal
311
+ def self.update_attribute_principal(username, attributes)
312
+ attributes.each do |key, val|
313
+ response = authenticated_connection do
314
+ if val.is_a?(Array)
315
+ valArray = ArrayOfString.new(val)
316
+ else
317
+ valArray = ArrayOfString.new
318
+ valArray << val
319
+ end
320
+
321
+ tuple = SOAPAttribute.new(key, valArray)
322
+ arg = UpdatePrincipalAttribute.new(@@application_token, username, tuple)
323
+
324
+ server.updatePrincipalAttribute(arg)
325
+ end
326
+
327
+ case response
328
+ when UpdatePrincipalAttributeResponse
329
+ # Burying as it worked
330
+ when ObjectNotFoundException
331
+ raise AuthenticationObjectNotFoundException
332
+ else
333
+ raise AuthenticationException, response
334
+ end
335
+ end
336
+
337
+ true
338
+ end
339
+
340
+ ##
341
+ # Remove principal
342
+ def self.remove_principal(username)
343
+ response = authenticated_connection do
344
+ arg = RemovePrincipal.new(@@application_token, username)
345
+ server.removePrincipal(arg)
346
+ end
347
+
348
+ case response
349
+ when RemovePrincipalResponse
350
+ return true
351
+ when ObjectNotFoundException
352
+ raise AuthenticationObjectNotFoundException
353
+ else
354
+ raise AuthenticationException, response
355
+ end
356
+ end
357
+
358
+ ##
359
+ # Find all principal names
360
+ def self.find_all_principal_names
361
+ response = authenticated_connection do
362
+ arg = FindAllPrincipalNames.new(@@application_token)
363
+ server.findAllPrincipalNames(arg)
364
+ end
365
+
366
+ case response
367
+ when FindAllPrincipalNamesResponse
368
+ return response.out
369
+ when ObjectNotFoundException
370
+ return {}
371
+ else
372
+ raise AuthenticationException, response
373
+ end
374
+ end
375
+
376
+ ##
377
+ # Find all role names
378
+ def self.find_all_role_names
379
+ response = authenticated_connection do
380
+ arg = FindAllRoleNames.new(@@application_token)
381
+ server.findAllRoleNames(arg)
382
+ end
383
+
384
+ case response
385
+ when FindAllRoleNamesResponse
386
+ return response.out
387
+ when ObjectNotFoundException
388
+ return {}
389
+ else
390
+ raise AuthenticationException, response
391
+ end
392
+ end
393
+
394
+ ##
395
+ # Add Role
396
+ def self.add_role(name, description, is_active)
397
+ response = authenticated_connection do
398
+ role = SOAPRole.new(nil, is_active, nil, nil, description, nil, nil, nil, name)
399
+ arg = AddRole.new(@@application_token, role)
400
+ server.addRole(arg)
401
+ end
402
+
403
+ case response
404
+ when AddRoleResponse
405
+ return true
406
+ when ObjectNotFoundException
407
+ return AuthenticationObjectNotFoundException
408
+ else
409
+ raise AuthenticationException, response
410
+ end
411
+ end
412
+
413
+ ##
414
+ # Add Principal to Role
415
+ def self.add_principal_to_role(username, role)
416
+ response = authenticated_connection do
417
+ arg = AddPrincipalToRole.new(@@application_token, username, role)
418
+ #raise arg.to_yaml
419
+ server.addPrincipalToRole(arg)
420
+ end
421
+
422
+ case response
423
+ when AddPrincipalToRoleResponse
424
+ return true
425
+ when ObjectNotFoundException
426
+ return AuthenticationObjectNotFoundException
427
+ else
428
+ raise AuthenticationException, response
429
+ end
430
+ end
431
+
432
+ ##
433
+ # Remove Principal form Role
434
+ def self.remove_principal_from_role(username, role)
435
+ response = authenticated_connection do
436
+ arg = RemovePrincipalFromRole.new(@@application_token, username, role)
437
+ server.removePrincipalFromRole(arg)
438
+ end
439
+
440
+ case response
441
+ when RemovePrincipalFromRoleResponse
442
+ return true
443
+ when ObjectNotFoundException
444
+ return AuthenticationObjectNotFoundException
445
+ else
446
+ raise AuthenticationException, response
447
+ end
448
+ end
449
+
450
+ ##
451
+ # Is Role Member
452
+ def self.is_role_member(username, role)
453
+ response = authenticated_connection do
454
+ arg = IsRoleMember.new(@@application_token, username, role )
455
+ server.isRoleMember(arg)
456
+ end
457
+
458
+ case response
459
+ when IsRoleMemberResponse
460
+ return response.out
461
+ when ObjectNotFoundException
462
+ return AuthenticationObjectNotFoundException
463
+ else
464
+ raise AuthenticationException, response
465
+ end
466
+ end
467
+
468
+
469
+
470
+ ##
471
+ # Remove Role
472
+ def self.remove_role(role)
473
+ response = authenticated_connection do
474
+ arg = RemoveRole.new(@@application_token, role)
475
+ server.removeRole(arg)
476
+ end
477
+
478
+ case response
479
+ when RemoveRoleResponse
480
+ return true
481
+ when ObjectNotFoundException
482
+ return AuthenticationObjectNotFoundException
483
+ else
484
+ raise AuthenticationException, response
485
+ end
486
+ end
487
+
488
+ ##
489
+ # Is Group Member
490
+ def self.is_group_member(username, group)
491
+ response = authenticated_connection do
492
+ arg = IsGroupMember.new(@@application_token, group, username )
493
+ server.isGroupMember(arg)
494
+ end
495
+
496
+ case response
497
+ when IsGroupMemberResponse
498
+ return response.out
499
+ when ObjectNotFoundException
500
+ return AuthenticationObjectNotFoundException
501
+ else
502
+ raise AuthenticationException, response
503
+ end
504
+ end
505
+
506
+ def self.find_all_group_names
507
+ response = authenticated_connection do
508
+ arg = FindAllGroupNames.new(@@application_token)
509
+ server.findAllGroupNames(arg)
510
+ end
511
+
512
+ case response
513
+ when FindAllGroupNamesResponse
514
+ return response.out
515
+ when ObjectNotFoundException
516
+ return AuthenticationObjectNotFoundException
517
+ else
518
+ raise AuthenticationException, response
519
+ end
520
+ end
521
+
522
+ def self.find_all_groups_for_principal(username)
523
+ response = authenticated_connection do
524
+ arg = FindGroupMemberships.new(@@application_token, username)
525
+ server.findGroupMemberships(arg)
526
+ end
527
+
528
+ case response
529
+ when FindGroupMembershipsResponse
530
+ return response.out
531
+ when ObjectNotFoundException
532
+ return AuthenticationObjectNotFoundException
533
+ else
534
+ raise AuthenticationException, response
535
+ end
536
+ end
537
+
538
+ def self.get_group(name)
539
+ response = authenticated_connection do
540
+ arg = FindGroupByName.new(@@application_token, name)
541
+ server.findGroupByName(arg)
542
+ end
543
+
544
+ case response
545
+ when FindGroupByNameResponse
546
+ return response.out
547
+ when ObjectNotFoundException
548
+ return AuthenticationObjectNotFoundException
549
+ else
550
+ raise AuthenticationException, response
551
+ end
552
+ end
553
+
554
+ private
555
+
556
+ # Parse the user
557
+ def self.parse_principal(rp)
558
+ p = {}
559
+ p[:id] = rp.iD
560
+ p[:active] = rp.active
561
+ p[:conception] = rp.conception
562
+ p[:description] = rp.description
563
+ p[:directoryID] = rp.directoryId
564
+ p[:lastModified] = rp.lastModified
565
+ p[:name] = rp.name
566
+
567
+ p[:attributes] = {}
568
+
569
+ rp.attributes.each do |attr|
570
+ case attr.values.size
571
+ when 0
572
+ p[:attributes][attr.name.to_sym] = nil
573
+ when 1
574
+ p[:attributes][attr.name.to_sym] = attr.values[0]
575
+ else
576
+ p[:attributes][attr.name.to_sym] = attr.values.to_a
577
+ end
578
+ end
579
+
580
+ return p
581
+ end
582
+
583
+ # Create ArrayOfValidationFactor from a ruby key=>value hash
584
+ def self.helper_validation_factors(validation_factors = {})
585
+ aovf = ArrayOfValidationFactor.new
586
+ validation_factors.each { |name,value| aovf << ValidationFactor.new(name, value)}
587
+ end
588
+
589
+ # Has the application been authenticated?
590
+ def self.application_auth_check
591
+ authenticate_application if @@application_token.nil?
592
+ end
593
+
594
+ # Shorthand for getting the security server object
595
+ def self.server
596
+ @@server ||= SecurityServerPortType.new(@@crowd_url)
597
+ end
598
+
599
+ # Wrapper for catching common exceptions. Also allows the application a chance
600
+ # to re-authenticate if the token is invalid.
601
+ def self.authenticated_connection
602
+ raise ArgumentError unless block_given?
603
+
604
+ application_auth_check
605
+
606
+ response = yield
607
+ rescue AuthenticationException => e
608
+ # Push the response into the exception message
609
+ raise AuthenticationException, e
610
+ rescue Errno::ECONNREFUSED => e
611
+ raise AuthenticationConnectionException, e
612
+ rescue ::SOAP::FaultError => e
613
+ # We'll retry once more on any fault.
614
+ begin
615
+ authenticate_application
616
+ response = yield
617
+ rescue AuthenticationException => e
618
+ raise AuthenticationException, e
619
+ rescue Errno::ECONNREFUSED => e
620
+ raise AuthenticationConnectionException, e
621
+ rescue Exception => e
622
+ raise AuthenticationException, e
623
+ end
624
+ rescue Exception => e
625
+ raise AuthenticationException, e
626
+ ensure
627
+ if response.is_a?(InvalidAuthorizationTokenException)
628
+ authenticate_application
629
+ response = yield
630
+ end
631
+ response
632
+ end
633
+
634
+ ##
635
+ # Returns the domain configured in Crowd or null if no domain has been set.
636
+ #
637
+ # *Deprecated:* This method has been superceded by get_cookie_config.
638
+ def self.get_domain
639
+ response = authenticated_connection do
640
+ arg = GetDomain.new(@@application_token)
641
+ server.getDomain(arg)
642
+ end
643
+
644
+ case response
645
+ when GetDomainResponse
646
+ return response.out
647
+ else
648
+ raise AuthenticationException, response
649
+ end
650
+ end
651
+
652
+ ##
653
+ # Updates the password credential for a principal who is in the application's assigned directory.
654
+ def self.update_principal_credential(principal, password)
655
+ response = authenticated_connection do
656
+ hash = Digest::SHA512.new.update(password).digest
657
+ cred = PasswordCredential.new(Base64::encode64(hash).gsub(/\n/, ''))
658
+ arg = UpdatePrincipalCredential.new(@@application_token, principal, cred)
659
+ server.updatePrincipalCredential(arg)
660
+ end
661
+
662
+ case response
663
+ when UpdatePrincipalCredentialResponse
664
+ return nil
665
+ else
666
+ raise AuthenticationException, response
667
+ end
668
+ end
669
+ end