faction 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a931eff98f4c85176316bb01e874a499ff64119e
4
+ data.tar.gz: 33ad1322fefac0622f9043cca4eece16092eb8ab
5
+ SHA512:
6
+ metadata.gz: 104284e288f39af5d3f9e2d4ce787c2834914de352b92cfa91b0ceaffafc2c35f88c90274c07a3a6056d11523ef942545ac9d8acf4b86fa89b7685801122ee25
7
+ data.tar.gz: 6d08fdedba41b27d5273a693ffebbaad304fbeee69a283180a4edcdbbf6a3ccee6571a64764faf0e389c4a927ba60d6aa07b69b299c10707c23873810dfa17a4
@@ -0,0 +1,4 @@
1
+ *~
2
+ *.gem
3
+ doc/
4
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in faction.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Olli Helenius
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # Faction
2
+
3
+ Faction is a client for Atlassian Crowd's SOAP API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'faction', github: 'onesto/faction'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install faction
18
+
19
+ ## Usage
20
+
21
+ ### User authentication
22
+
23
+ ```ruby
24
+ token = begin
25
+ crowd = Faction::Client.new('http://localhost:8050/crowd/services/SecurityServer', 'myapp', 'secret')
26
+ validation_factors = {:user_agent => "Some web browser",
27
+ :remote_address => "127.0.0.1"}
28
+ crowd.authenticate_principal('test-user', 'secret-password', validation_factors)
29
+ rescue Faction::AuthenticationException => e
30
+ puts "Authentication failed: #{e}"
31
+ end
32
+ # .. do something with token ..
33
+ ```
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ require 'faction'
2
+
3
+ CROWD_URL = 'http://localhost:8085/crowd/services/SecurityServer'
4
+ APP_NAME = 'application'
5
+ APP_PASSWORD = 'password'
6
+
7
+ TEST_USERNAME = 'test'
8
+ TEST_PASSWORD = 'test'
9
+
10
+ crowd = Faction::Client.new(CROWD_URL, APP_NAME, APP_PASSWORD, :verify_cert => false)
11
+ token = crowd.authenticate_principal(TEST_USERNAME, TEST_PASSWORD)
12
+ puts("#{TEST_USERNAME} logged in, token = #{token}")
13
+ crowd.invalidate_principal_token(token)
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'faction/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "faction"
8
+ spec.version = Faction::VERSION
9
+ spec.authors = ["Olli Helenius"]
10
+ spec.email = ["olli.helenius@onesto.fi"]
11
+ spec.description = %q{A simple Savon-based client for Atlassian Crowd SOAP API}
12
+ spec.summary = %q{A simple Savon-based client for Atlassian Crowd SOAP API}
13
+ spec.homepage = "https://github.com/onesto/faction"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'savon', '< 0.8'
25
+ end
@@ -0,0 +1,289 @@
1
+ require 'openssl'
2
+ require 'savon'
3
+ require "faction/version"
4
+
5
+
6
+ module Faction #:nodoc:
7
+
8
+ # Exception base for Faction
9
+ class Exception < ::Exception; end
10
+ # Any sort of Exception from Crowd
11
+ class AuthenticationException < Exception; end
12
+
13
+ # See http://docs.atlassian.com/crowd/current/com/atlassian/crowd/integration/service/soap/server/SecurityServer.html
14
+ # for Crowd SOAP API documentation.
15
+ #
16
+ # The <tt>validation_factors</tt> parameter is a <tt>Hash</tt> that can contain the following keys:
17
+ # * <tt>:user_agent</tt> - <tt>ValidationFactor.USER_AGENT</tt>
18
+ # * <tt>:remote_address</tt> - <tt>ValidationFactor.REMOTE_ADDRESS</tt>
19
+ # * <tt>:remote_host</tt> - <tt>ValidationFactor.REMOTE_HOST</tt>
20
+ # * <tt>:forwarded_for</tt> - <tt>ValidationFactor.X_FORWARDED_FOR</tt>
21
+ # * <tt>:random_number</tt> - <tt>ValidationFactor.RANDOM_NUMBER</tt>
22
+ # * <tt>:name</tt> - <tt>ValidationFactor.NAME</tt>
23
+ class Client
24
+ @@debug = false
25
+
26
+ # Sets the global debugging for Client
27
+ def self.debug=(value)
28
+ @@debug = value
29
+ end
30
+
31
+ # True if Faction is in debug mode
32
+ def self.debug?
33
+ @@debug
34
+ end
35
+
36
+ # Url of the Crowd SecurityServer
37
+ attr_reader :crowd_url
38
+ # Application name
39
+ attr_reader :app_name
40
+ # Application password
41
+ attr_reader :app_password
42
+
43
+ # Instantiates a new client using a "standard" <tt>crowd.properties</tt> file.
44
+ def self.from_properties(properties_file)
45
+ props = Hash[*open(properties_file).readlines.map {|line|
46
+ line.split(/=/, 2).map(&:strip)}.flatten]
47
+ self.new(props['crowd.server.url'] + '/SecurityServer',
48
+ props['application.name'],
49
+ props['application.password'],
50
+ :verify_cert => ['yes', 'true'].include?(props['ssl.verify']))
51
+ end
52
+
53
+ # Creates a new Crowd client.
54
+ #
55
+ # Parameters:
56
+ # * <tt>crowd_url</tt> - The URL to Crowd SecurityServer.
57
+ # * <tt>app_name</tt> - Application name.
58
+ # * <tt>app_password</tt> - Application password.
59
+ # * <tt>options</tt> - A Hash of options (described below).
60
+ #
61
+ # Options
62
+ # * <tt>:verify_cert</tt> - If <tt>false</tt> the peer SSL certificate is not verified.
63
+ #
64
+ # Example:
65
+ # Faction::Client.new('http://localhost:8085/crowd/services/SecurityServer', 'application', 'password')
66
+ #
67
+ def initialize(crowd_url, app_name, app_password, options = {})
68
+ @crowd_url = crowd_url
69
+ @crowd = Savon::Client.new(@crowd_url + (Client.debug? ? '?wsdl' : ''))
70
+ if options[:verify_cert] == false
71
+ @crowd.request.http.ssl_client_auth(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
72
+ end
73
+ @app_name = app_name
74
+ @app_password = app_password
75
+ @app_token = nil
76
+ end
77
+
78
+ # See <tt>SecurityServerClient.authenticatePrincipal</tt>
79
+ def authenticate_principal(name, password, validation_factors = nil)
80
+ authenticated_crowd_call(:authenticate_principal,
81
+ { 'auth:application' => app_name,
82
+ 'auth:name' => name,
83
+ 'auth:credential' => {'auth:credential' => password},
84
+ 'auth:validationFactors' => convert_validation_factors(validation_factors)})
85
+ end
86
+
87
+ # See <tt>SecurityServerClient.createPrincipalToken</tt>
88
+ def create_principal_token(name, validation_factors = nil)
89
+ authenticated_crowd_call(:create_principal_token,
90
+ { 'auth:application' => app_name,
91
+ 'auth:name' => name,
92
+ 'auth:validationFactors' => convert_validation_factors(validation_factors)})
93
+ end
94
+
95
+ # See <tt>SecurityServerClient.invalidatePrincipalToken</tt>
96
+ def invalidate_principal_token(token)
97
+ authenticated_crowd_call(:invalidate_principal_token, token) && nil
98
+ end
99
+
100
+ # See <tt>SecurityServerClient.getCookieInfo</tt>
101
+ # Returns a <tt>Hash</tt> containing the cookie info fields.
102
+ def cookie_info
103
+ authenticated_crowd_call(:get_cookie_info)
104
+ end
105
+
106
+ def get_cookie_info
107
+ $stderr.puts('faction: get_cookie_info deprecated, use cookie_info instead')
108
+ cookie_info
109
+ end
110
+
111
+ # See <tt>SecurityServerClient.isValidPrincipalToken</tt>
112
+ def valid_principal_token?(token, validation_factors = nil)
113
+ authenticated_crowd_call(:is_valid_principal_token,
114
+ token,
115
+ {'auth:validationFactors' => convert_validation_factors(validation_factors)})
116
+ end
117
+
118
+ # See <tt>SecurityServer.updatePrincipalCredential</tt>
119
+ def update_principal_credential(name, new_password)
120
+ authenticated_crowd_call(:update_principal_credential,
121
+ name,
122
+ {'auth:credential' => new_password, 'auth:encryptedCredential' => false})
123
+ end
124
+
125
+ # See <tt>SecurityServer.findPrincipalByToken</tt>
126
+ # Returns the principal information as a <tt>Hash</tt>.
127
+ def find_principal_by_token(token)
128
+ simplify_soap_attributes(authenticated_crowd_call(:find_principal_by_token, token))
129
+ end
130
+
131
+ def find_principal_by_name(name)
132
+ simplify_soap_attributes(authenticated_crowd_call(:find_principal_by_name, name))
133
+ end
134
+
135
+ def find_principal_with_attributes_by_name(name)
136
+ simplify_soap_attributes(authenticated_crowd_call(:find_principal_with_attributes_by_name, name))
137
+ end
138
+
139
+ def find_group_by_name(name)
140
+ simplify_soap_group(authenticated_crowd_call(:find_group_by_name, name))
141
+ end
142
+
143
+ def find_group_memberships(name)
144
+ arrayify(authenticated_crowd_call(:find_group_memberships, name)[:string])
145
+ end
146
+
147
+ def group_names
148
+ authenticated_crowd_call(:find_all_group_names)[:string]
149
+ end
150
+
151
+ def principal_names
152
+ authenticated_crowd_call(:find_all_principal_names)[:string]
153
+ end
154
+
155
+ def granted_authorities
156
+ authenticated_crowd_call(:get_granted_authorities)[:string]
157
+ end
158
+
159
+ def cache_enabled?
160
+ authenticated_crowd_call(:is_cache_enabled)
161
+ end
162
+
163
+ def cache_time
164
+ authenticated_crowd_call(:get_cache_time)
165
+ end
166
+
167
+ def add_principal_to_group(principal, group)
168
+ authenticated_crowd_call(:add_principal_to_group, principal, group) && nil
169
+ end
170
+
171
+ private
172
+
173
+ CROWD_NAMESPACES = {
174
+ 'xmlns:wsdl' => 'urn:SecurityServer',
175
+ 'xmlns:auth' => 'http://authentication.integration.crowd.atlassian.com',
176
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
177
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
178
+ }
179
+
180
+ USER_AGENT = "User-Agent" #:nodoc:
181
+ REMOTE_ADDRESS = "remote_address" #:nodoc:
182
+ REMOTE_HOST = "remote_host" #:nodoc:
183
+ X_FORWARDED_FOR = "X-Forwarded-For" #:nodoc:
184
+ RANDOM_NUMBER = "Random-Number" #:nodoc:
185
+ NAME = "NAME" #:nodoc:
186
+
187
+ VALIDATION_FACTOR_MAPPING = {
188
+ :user_agent => USER_AGENT,
189
+ :remote_address => REMOTE_ADDRESS,
190
+ :remote_host => REMOTE_HOST,
191
+ :forwarded_for => X_FORWARDED_FOR,
192
+ :random_number => RANDOM_NUMBER,
193
+ :name => NAME
194
+ }
195
+
196
+ def convert_validation_factors(in_validation_factors)
197
+ return nil if in_validation_factors.nil?
198
+ result = in_validation_factors.map do |name, value|
199
+ raise Faction::Exception, "Invalid validation factor #{name}" if !VALIDATION_FACTOR_MAPPING.include?(name)
200
+ {'auth:name' => VALIDATION_FACTOR_MAPPING[name], 'auth:value' => value}
201
+ end
202
+ {'auth:ValidationFactor' => result}
203
+ end
204
+
205
+ def simplify_soap_attributes(soap_object)
206
+ attributes = soap_object[:attributes][:soap_attribute].inject({}) do |hash, item|
207
+ hash[item[:name].to_sym] = item[:values][:string]
208
+ hash
209
+ end
210
+ soap_object.merge(:attributes => attributes)
211
+ end
212
+
213
+ def simplify_soap_group(soap_group)
214
+ soap_group.merge(:members => soap_group[:members][:string])
215
+ end
216
+
217
+ def arrayify(soap_object)
218
+ if soap_object.nil?
219
+ []
220
+ elsif soap_object.is_a? Array
221
+ soap_object
222
+ else
223
+ [soap_object]
224
+ end
225
+ end
226
+
227
+ def app_authentication
228
+ Hash['auth:name' => app_name,
229
+ 'auth:token' => app_token]
230
+ end
231
+
232
+ def ensure_app_token!
233
+ app_token
234
+ end
235
+
236
+ def app_token
237
+ @app_token ||= authenticate_application
238
+ end
239
+
240
+ def authenticate_application
241
+ response = crowd_call(:authenticate_application) do |soap|
242
+ soap.body.merge!('wsdl:in0' => {
243
+ 'auth:name' => app_name,
244
+ 'auth:credential' => {
245
+ 'auth:credential' => app_password,
246
+ 'auth:encryptedCredential' => false,
247
+ }})
248
+ end
249
+ response[:token]
250
+ end
251
+
252
+ def crowd_call(name, &block)
253
+ method = (Client.debug? ? name : "#{name}!").to_sym
254
+ response = @crowd.call(method) do |soap|
255
+ soap.namespaces.merge!(CROWD_NAMESPACES)
256
+ soap.body = {}
257
+ yield soap if block_given?
258
+ end
259
+ response.to_hash[:"#{name}_response"][:out]
260
+ end
261
+
262
+ def real_authenticated_crowd_call(name, *args)
263
+ ensure_app_token!
264
+ crowd_call(name) do |soap|
265
+ soap.body = {'wsdl:in0' => app_authentication}
266
+ order = ['wsdl:in0']
267
+ args.each_with_index do |arg, index|
268
+ soap.body["wsdl:in#{index + 1}"] = arg
269
+ order << "wsdl:in#{index + 1}"
270
+ end
271
+ soap.body[:order!] = order
272
+ end
273
+ end
274
+
275
+ def authenticated_crowd_call(name, *args)
276
+ begin
277
+ real_authenticated_crowd_call(name, *args)
278
+ rescue Savon::SOAPFault => f
279
+ # retry once
280
+ @app_token = nil
281
+ begin
282
+ real_authenticated_crowd_call(name, *args)
283
+ rescue Savon::SOAPFault => f
284
+ raise AuthenticationException, f.message
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,3 @@
1
+ module Faction
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faction
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Olli Helenius
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: savon
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - <
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - <
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ description: A simple Savon-based client for Atlassian Crowd SOAP API
56
+ email:
57
+ - olli.helenius@onesto.fi
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - examples/log_in_and_out.rb
68
+ - faction.gemspec
69
+ - lib/faction.rb
70
+ - lib/faction/version.rb
71
+ homepage: https://github.com/onesto/faction
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.0.3
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: A simple Savon-based client for Atlassian Crowd SOAP API
95
+ test_files: []