usvn-crowd-sync 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.1)
11
+ rcov (0.9.9)
12
+ rspec (2.3.0)
13
+ rspec-core (~> 2.3.0)
14
+ rspec-expectations (~> 2.3.0)
15
+ rspec-mocks (~> 2.3.0)
16
+ rspec-core (2.3.1)
17
+ rspec-expectations (2.3.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.3.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.5.2)
27
+ rcov
28
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 crazycode
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = usvn-crowd-sync
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to usvn-crowd-sync
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 crazycode. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "usvn-crowd-sync"
16
+ gem.executables = %W(usvn-crowd-sync)
17
+
18
+ gem.homepage = "http://github.com/crazycode/usvn-crowd-sync"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Sync Atlassian crowd\'s user & groups to USVN.}
21
+ gem.description = %Q{Sync Atlassian crowd\'s user & groups to USVN.}
22
+ gem.email = "crazycode@gmail.com"
23
+ gem.authors = ["crazycode"]
24
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
25
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
26
+ gem.add_runtime_dependency 'data_mapper'
27
+ gem.add_runtime_dependency 'dm-mysql-adapter'
28
+ # gem.add_runtime_dependency 'crowd'
29
+ gem.add_development_dependency 'rspec'
30
+ end
31
+ Jeweler::RubygemsDotOrgTasks.new
32
+
33
+ require 'rspec/core'
34
+ require 'rspec/core/rake_task'
35
+ RSpec::Core::RakeTask.new(:spec) do |spec|
36
+ spec.pattern = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
40
+ spec.pattern = 'spec/**/*_spec.rb'
41
+ spec.rcov = true
42
+ end
43
+
44
+ task :default => :spec
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "usvn-crowd-sync #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ require 'rubygems'
4
+ require 'data_mapper'
5
+
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+
10
+ require 'crowd'
11
+
12
+ require 'usvn-crowd-sync'
13
+ require 'open-uri'
14
+
15
+ CROWD_CONFIG_PATH='/etc/usvn-crowd-sync.yml'
16
+
17
+ config = YAML.load(File.new(CROWD_CONFIG_PATH))
18
+
19
+ DataMapper.setup(:default, config["usvn_db"])
20
+
21
+ crowd = Crowd.new(config)
22
+ crowd_group_names = crowd.find_all_group_names
23
+
24
+ crowd_group_names.each do |g|
25
+ puts g
26
+ end
27
+
28
+ ug_hashcode1 = 0
29
+ GroupUser.all.each { |ug| ug_hashcode1 += ug.hash }
30
+
31
+ cached_user_hash = Hash.new
32
+ User.all.each { |u| cached_user_hash[u.login] = u }
33
+
34
+ groups = Group.create_or_update(crowd_group_names)
35
+
36
+ User.all(:password => 'a9asdf907aafeqwerqw').each do |u|
37
+ GroupUser.all(:users_id => u.id).destroy
38
+ end
39
+
40
+
41
+ crowd_group_names.each do |gname|
42
+ begin
43
+ user_names = crowd.find_users_in_group(gname)
44
+ g = Group.first(:name => gname.strip)
45
+
46
+ unless g.nil?
47
+ user_names.each do |name|
48
+ begin
49
+ u = cached_user_hash[name] || User.first(:login => name)
50
+ if u.nil?
51
+ crowd_user = crowd.find_by_name(name)
52
+ u = User.create(:login => crowd_user.name,
53
+ :firstname => crowd_user.name,
54
+ :lastname => crowd_user.full_name,
55
+ :email => crowd_user.email,
56
+ :password => 'a9asdf907aafeqwerqw',
57
+ :secret_id => 'a9asdf907aafeqwerqw',
58
+ :is_admin => 0)
59
+ cached_user_hash[u.login] = u
60
+ end
61
+ ug = GroupUser.first(:users_id => u.id, :groups_id => g.id)
62
+ if ug.nil?
63
+ GroupUser.create(:users_id => u.id, :groups_id => g.id, :is_leader => 0)
64
+ end
65
+ rescue Exception => e1
66
+ puts "User Not found #{name}: #{e1}"
67
+ end
68
+ end
69
+ end
70
+ rescue Exception => e
71
+ puts "Not found #{gname}: #{e}"
72
+ end
73
+ end
74
+
75
+ ug_hashcode2 = 0
76
+ GroupUser.all.each { |ug| ug_hashcode2 += ug.hash }
77
+
78
+ if ug_hashcode2 == ug_hashcode1
79
+ puts "Nothing Changed! before=#{ug_hashcode1}, after=#{ug_hashcode2}"
80
+ else
81
+ usvn_home = config['usvn_home']
82
+ #regenerate authz file
83
+ if File.exists?("#{usvn_home}/tools/regenerate_authz_file.php")
84
+ system("cd #{usvn_home} && php #{usvn_home}/tools/regenerate_authz_file.php #{usvn_home}/config.ini")
85
+ else
86
+ puts "File #{usvn_home}/tools/regenerate_authz_file.php DOES NOT Exists!"
87
+ end
88
+ end
data/lib/crowd.rb ADDED
@@ -0,0 +1,18 @@
1
+ # ===========================================================================
2
+ # Copyright (C) 2009, Progress Software Corporation and/or its
3
+ # subsidiaries or affiliates. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # ===========================================================================
17
+ require 'crowd/crowd'
18
+ require 'crowd/crowd_authentication'
@@ -0,0 +1,305 @@
1
+ # ===========================================================================
2
+ # Copyright (C) 2009, Progress Software Corporation and/or its
3
+ # subsidiaries or affiliates. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # ===========================================================================
17
+
18
+ #!/usr/bin/env ruby
19
+ require 'rubygems'
20
+ gem 'soap4r'
21
+ require 'yaml'
22
+ require 'crowd/defaultDriver.rb'
23
+
24
+ # Wrapper around Atlassian Crowd's SOAP API
25
+ #-- TODO Should this be a module rather than a class?
26
+ class Crowd
27
+
28
+ # testing only
29
+ # attr_reader :server, :server_token
30
+
31
+ def initialize( config=nil )
32
+ if config
33
+ @configuration = config
34
+ else
35
+ # TODO die if configuration file is missing
36
+ @configuration = configuration
37
+ end
38
+
39
+ crowd_server_url = @configuration['crowd_server_url']
40
+ @server = SecurityServerPortType.new(crowd_server_url)
41
+
42
+ # run ruby with -d to see SOAP wiredumps.
43
+ @server.wiredump_dev = STDERR if $DEBUG or @debug
44
+
45
+ # retries = 0
46
+ # begin
47
+ authenticate_application @configuration['application_name'], @configuration['application_password']
48
+ # rescue SOAP::EmptyResponseError
49
+ # if retries < 5
50
+ # retry
51
+ # else
52
+ # raise
53
+ # end
54
+ # end
55
+ end
56
+
57
+ # TODO interpret errors and throw more useful exceptions Invalid Username, Invalid Password, Not Authorized, No such application etc
58
+ # a token is returned which can be set in cookie crowd.token_key
59
+ def authenticate_user(user_name, password, application = nil, user_agent = nil, remote_ip = nil)
60
+
61
+ if not application
62
+ application = @server_token.name
63
+ end
64
+
65
+ validation_factors = create_validation_factors user_agent, remote_ip
66
+ auth_context = PrincipalAuthenticationContext.new application, PasswordCredential.new(password), user_name, validation_factors
67
+ auth_principal = AuthenticatePrincipal.new @server_token, auth_context
68
+ response = @server.authenticatePrincipal(auth_principal)
69
+ user_token = response.out
70
+ end
71
+
72
+ def valid_user_token?(user_token, user_agent = nil, remote_ip = nil)
73
+ validation_factors = create_validation_factors user_agent, remote_ip
74
+
75
+ response = @server.isValidPrincipalToken(IsValidPrincipalToken.new(@server_token, user_token, validation_factors))
76
+ response.out
77
+ end
78
+
79
+ # it's not clear what this function does
80
+ def invalidate_user_token(user_name)
81
+ @server.invalidatePrincipalToken(InvalidatePrincipalToken.new(@server_token, user_name))
82
+ end
83
+
84
+ # this will come from crowd.token_key
85
+ def find_by_token(user_token)
86
+ reponse = @server.findPrincipalByToken(FindPrincipalByToken.new(@server_token, user_token))
87
+ CrowdUser.new reponse.out
88
+ end
89
+
90
+ def find_by_name(user_name)
91
+ reponse = @server.findPrincipalByName(FindPrincipalByName.new(@server_token, user_name))
92
+ CrowdUser.new reponse.out
93
+ end
94
+
95
+ # add a new user to the directory
96
+ def add_user(user_name, email, first_name, last_name, password, attributes = {})
97
+
98
+ principal = SOAPPrincipal.new
99
+ # TODO probably want to set the directory id
100
+ principal.name = user_name
101
+ principal.active = true
102
+
103
+ principal.attributes = ArrayOfSOAPAttribute.new
104
+ # unfortunately these attributes are directory specific, need a more generic solution
105
+ principal.attributes.push SOAPAttribute.new("mail", ArrayOfString.new(1) { email })
106
+ principal.attributes.push SOAPAttribute.new("givenName", ArrayOfString.new(1) { first_name })
107
+ principal.attributes.push SOAPAttribute.new("sn", ArrayOfString.new(1) { last_name })
108
+
109
+ attributes.each do |key,value|
110
+ principal.attributes.push SOAPAttribute.new(key, ArrayOfString.new(1) { value })
111
+ end
112
+
113
+ parameters = AddPrincipal.new @server_token, principal, PasswordCredential.new(password)
114
+
115
+ response = @server.addPrincipal parameters
116
+ CrowdUser.new response.out
117
+ end
118
+
119
+ def delete_user!(user_name)
120
+ @server.removePrincipal(RemovePrincipal.new(@server_token, user_name))
121
+ end
122
+
123
+ def reset_password!(user_name)
124
+ @server.resetPrincipalCredential(ResetPrincipalCredential.new(@server_token, user_name))
125
+ end
126
+
127
+ def change_password(user_name, password)
128
+ @server.updatePrincipalCredential(UpdatePrincipalCredential.new(@server_token, user_name, PasswordCredential.new(password)))
129
+ end
130
+
131
+ def update_user_attribute(user_name, attribute_name, attribute_value)
132
+ # might need to handle additional types in the future
133
+ soap_attribute = SOAPAttribute.new(attribute_name, ArrayOfString.new(1) { attribute_value })
134
+ @server.updatePrincipalAttribute UpdatePrincipalAttribute.new(@server_token, user_name, soap_attribute)
135
+ end
136
+
137
+ def add_group(group_name, group_description="")
138
+ group = SOAPGroup.new
139
+ group.active = true
140
+ group.name = group_name
141
+ group.description = group_description
142
+ response = @server.addGroup(AddGroup.new(@server_token, group))
143
+ response.out
144
+ end
145
+
146
+ def delete_group!(group_name)
147
+ @server.removeGroup(RemoveGroup.new(@server_token, group_name))
148
+ end
149
+
150
+ def find_all_group_names()
151
+ response = @server.findAllGroupNames(FindAllGroupNames.new(@server_token))
152
+ response.out
153
+ end
154
+
155
+ def find_users_in_group(group_name)
156
+ response = @server.findGroupByName(FindGroupByName.new(@server_token, group_name))
157
+ if response.out
158
+ return response.out.members
159
+ else
160
+ return nil
161
+ end
162
+ end
163
+
164
+ def add_user_to_group(user_name, group_name)
165
+ @server.addPrincipalToGroup(AddPrincipalToGroup.new(@server_token, user_name, group_name))
166
+ end
167
+
168
+ def remove_user_from_group(user_name, group_name)
169
+ @server.removePrincipalFromGroup(RemovePrincipalFromGroup.new(@server_token, user_name, group_name))
170
+ end
171
+
172
+ def group_member?(user_name, group_name)
173
+ response = @server.isGroupMember(IsGroupMember.new(@server_token, group_name, user_name))
174
+ response.out
175
+ end
176
+
177
+ def find_group_memberships(user_name)
178
+ response = @server.findGroupMemberships(FindGroupMemberships.new(@server_token, user_name))
179
+ response.out
180
+ end
181
+
182
+ # criteria is a hash
183
+ # supported keys are principal.name, principal.email, principal.active, principal.directory.id
184
+ # results can be limited by specifying search.max.results and search.index.start
185
+ # note that the actual search behavior is depends on the directory implementation
186
+ def search_users(criteria)
187
+
188
+ array_of_search_restriction = ArrayOfSearchRestriction.new
189
+
190
+ criteria.each do |key, value|
191
+ array_of_search_restriction << SearchRestriction.new(key, value)
192
+ end
193
+
194
+ response = @server.searchPrincipals(SearchPrincipals.new(@server_token, array_of_search_restriction))
195
+
196
+ crowd_users = Array.new
197
+ response.out.each do |principal|
198
+ crowd_users << CrowdUser.new(principal)
199
+ end
200
+
201
+ crowd_users
202
+
203
+ end
204
+
205
+ private
206
+
207
+ def authenticate_application(application_name, application_password)
208
+ auth_context = ApplicationAuthenticationContext.new PasswordCredential.new(application_password), application_name
209
+ auth_application = AuthenticateApplication.new auth_context
210
+ reponse = @server.authenticateApplication(auth_application)
211
+ @server_token = reponse.out
212
+ end
213
+
214
+ def create_validation_factors(user_agent, remote_ip)
215
+ validation_factors = ArrayOfValidationFactor.new
216
+ if user_agent
217
+ validation_factors << (ValidationFactor.new "User-Agent", user_agent)
218
+ end
219
+
220
+ if remote_ip
221
+ validation_factors << (ValidationFactor.new "remote_address", remote_ip)
222
+ end
223
+
224
+ # TODO Might need to handle X-Forwarded-For
225
+ validation_factors
226
+ end
227
+
228
+ def configuration
229
+ if defined?(CROWD_CONFIG_PATH)
230
+ config_file = CROWD_CONFIG_PATH
231
+ elsif defined?(RAILS_ROOT) and File.exists?(File.join(RAILS_ROOT, 'config/crowd.yml'))
232
+ config_file = File.join(RAILS_ROOT, 'config/crowd.yml')
233
+ else
234
+ config_file = File.join(File.dirname(__FILE__), 'crowd.yml')
235
+ end
236
+
237
+ YAML::load(IO.read(config_file))
238
+ end
239
+
240
+ end
241
+
242
+ # Wrapper around SOAPPrincipal which provides easy access to attributes
243
+ class CrowdUser
244
+ attr_reader :user_id
245
+ attr_reader :active
246
+ attr_reader :attributes
247
+ attr_reader :conception
248
+ attr_reader :description
249
+ attr_reader :directory_id
250
+ attr_reader :last_modified
251
+ attr_reader :name
252
+
253
+ #-- virtual
254
+ attr_reader :first_name, :last_name, :full_name, :email
255
+
256
+ def initialize(soap_principal)
257
+ @soap_principal = soap_principal
258
+
259
+ @user_id = soap_principal.iD
260
+ @active = soap_principal.active
261
+ @conception = soap_principal.conception
262
+ @description = soap_principal.description
263
+ @directory_id = soap_principal.directoryID
264
+ @last_modified = soap_principal.lastModified
265
+ @name = soap_principal.name
266
+
267
+ process_attributes_array soap_principal
268
+ end
269
+
270
+ # allow attributes to be fetched by name
271
+ #
272
+ # <tt>user.mail</tt> instead of <tt>user.attributes['mail']</tt>
273
+ #
274
+ # returns <tt>nil</tt> is the attibute does not exist
275
+ def method_missing(attribute_name)
276
+ @attributes["#{attribute_name}"]
277
+ end
278
+
279
+ def email #:nodoc:
280
+ @attributes["mail"]
281
+ end
282
+
283
+ def first_name #:nodoc:
284
+ return @attributes["givenName"]
285
+ end
286
+
287
+ def last_name #:nodoc:
288
+ return @attributes["sn"]
289
+ end
290
+
291
+ def full_name #:nodoc:
292
+ "#{first_name} #{last_name}"
293
+ end
294
+
295
+ private
296
+ # convert the SOAPAttribute[] into a {} for easier access
297
+ def process_attributes_array(soap_principal)
298
+ @attributes = Hash.new
299
+ soap_principal.attributes.each do |attribute|
300
+ # assuming there's only one value
301
+ @attributes[attribute.name] = attribute.values[0]
302
+ end
303
+ end
304
+
305
+ end