passaporteweb-client 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rest_client'
4
+ require 'multi_json'
5
+
6
+ require "passaporte_web/version"
7
+ require "passaporte_web/configuration"
8
+ require "passaporte_web/http"
9
+ require "passaporte_web/helpers"
10
+ require "passaporte_web/attributable"
11
+ require "passaporte_web/identity"
12
+ require "passaporte_web/service_account"
13
+ require "passaporte_web/service_account_member"
14
+ require "passaporte_web/identity_service_account"
15
+
16
+ module PassaporteWeb
17
+
18
+ def self.configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield(configuration) if block_given?
24
+ end
25
+
26
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+
4
+ module Attributable
5
+
6
+ def set_attributes(hash)
7
+ self.class::ATTRIBUTES.each do |attribute|
8
+ value = hash[attribute.to_s] if hash.has_key?(attribute.to_s)
9
+ value = hash[attribute.to_sym] if hash.has_key?(attribute.to_sym)
10
+ instance_variable_set("@#{attribute}".to_sym, value)
11
+ end
12
+ end
13
+ private :set_attributes
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require 'base64'
3
+ module PassaporteWeb
4
+
5
+ class Configuration
6
+ attr_accessor :url, :user_agent, :application_token, :application_secret, :user_token
7
+
8
+ def initialize
9
+ @url = 'https://app.passaporteweb.com.br'
10
+ @user_agent = "PassaporteWeb Ruby Client v#{PassaporteWeb::VERSION}"
11
+ @application_token = nil
12
+ @application_secret = nil
13
+ @user_token = nil
14
+ end
15
+
16
+ def application_credentials
17
+ check_tokens! :application_token, :application_secret
18
+ base64_credential('application', @application_token, @application_secret)
19
+ end
20
+
21
+ def user_credentials
22
+ check_tokens! :user_token
23
+ base64_credential('user', @user_token)
24
+ end
25
+
26
+ private
27
+
28
+ def check_tokens!(*tokens)
29
+ tokens.each do |token|
30
+ value = instance_variable_get("@#{token}".to_sym)
31
+ raise ArgumentError, "#{token} not set" if value.nil? || value.to_s.strip == ''
32
+ end
33
+ end
34
+
35
+ def base64_credential(type, user, password=nil)
36
+ return "Basic #{::Base64.strict_encode64("#{user}:#{password}")}" if type.eql? 'application'
37
+ return "Basic #{::Base64.strict_encode64(":#{user}")}" if type.eql? 'user'
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ require 'ostruct'
3
+ module PassaporteWeb
4
+
5
+ class Helpers
6
+
7
+ # Converts pagination information from a Link header in a HTTP response to a Hash
8
+ #
9
+ # Example:
10
+ # link_header = "<http://sandbox.app.passaporteweb.com.br/organizations/api/accounts/?page=3&limit=3>; rel=next, <http://sandbox.app.passaporteweb.com.br/organizations/api/accounts/?page=1&limit=3>; rel=prev, <http://sandbox.app.passaporteweb.com.br/organizations/api/accounts/?page=5&limit=3>; rel=last, <http://sandbox.app.passaporteweb.com.br/organizations/api/accounts/?page=1&limit=3>; rel=first"
11
+ # PassaporteWeb::Helpers.meta_links_from_header(link_header)
12
+ # => {limit: 3, next_page: 3, prev_page: 1, first_page: 1, last_page: 5}
13
+ def self.meta_links_from_header(link_header)
14
+ hash = {limit: nil, next_page: nil, prev_page: nil, first_page: nil, last_page: nil}
15
+ links = link_header.split(',').map(&:strip)
16
+
17
+ if link_header.match(/limit\=([0-9]+)/)
18
+ hash[:limit] = Integer($1)
19
+ end
20
+
21
+ links.each do |link|
22
+ if link.match(/page\=([0-9]+).*rel\=([a-z]+)/)
23
+ case $2
24
+ when 'next'
25
+ hash[:next_page] = Integer($1)
26
+ when 'prev'
27
+ hash[:prev_page] = Integer($1)
28
+ when 'first'
29
+ hash[:first_page] = Integer($1)
30
+ when 'last'
31
+ hash[:last_page] = Integer($1)
32
+ end
33
+ end
34
+ end
35
+
36
+ hash
37
+ end
38
+
39
+ # Converts a Hash recursevely to a OpenStruct object.
40
+ #
41
+ # Example:
42
+ # hash = {a: 1, b: {c: 3, d: 4, e: {f: 6, g: 7}}}
43
+ # os = PassaporteWeb::Helpers.convert_to_ostruct_recursive(hash)
44
+ # os.a # => 1
45
+ # os.b # => {c: 3, d: 4, e: {f: 6, g: 7}}
46
+ # os.b.c # => 3
47
+ # os.b.d # => 4
48
+ # os.b.e # => {f: 6, g: 7}
49
+ # os.b.e.f # => 6
50
+ # os.b.e.g # => 7
51
+ def self.convert_to_ostruct_recursive(obj, options={})
52
+ result = obj
53
+ if result.is_a? Hash
54
+ result = result.dup
55
+ result.each do |key, val|
56
+ result[key] = convert_to_ostruct_recursive(val, options) unless (!options[:exclude].nil? && options[:exclude].include?(key))
57
+ end
58
+ result = OpenStruct.new result
59
+ elsif result.is_a? Array
60
+ result = result.map { |r| convert_to_ostruct_recursive(r, options) }
61
+ end
62
+ return result
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ require 'base64'
3
+ module PassaporteWeb
4
+
5
+ class Http # :nodoc:
6
+
7
+ def self.get(path='/', params={}, type='application')
8
+ get_or_delete(:get, path, params, type)
9
+ end
10
+
11
+ def self.put(path='/', body={}, params={}, type='application')
12
+ put_or_post(:put, path, body, params, type)
13
+ end
14
+
15
+ def self.post(path='/', body={}, params={}, type='application')
16
+ put_or_post(:post, path, body, params, type)
17
+ end
18
+
19
+ def self.delete(path='/', params={}, type='application')
20
+ get_or_delete(:delete, path, params, type)
21
+ end
22
+
23
+ def self.custom_auth_get(user, password, path='/', params={})
24
+ credentials = "Basic #{::Base64.strict_encode64("#{user}:#{password}")}"
25
+ custom_params = common_params('application').merge({authorization: credentials})
26
+ RestClient.get(
27
+ pw_url(path),
28
+ {params: params}.merge(custom_params)
29
+ )
30
+ end
31
+
32
+ private
33
+
34
+ def self.put_or_post(method, path, body, params, type)
35
+ RestClient.send(
36
+ method,
37
+ pw_url(path),
38
+ encoded_body(body),
39
+ {params: params}.merge(common_params(type))
40
+ )
41
+ end
42
+
43
+ def self.get_or_delete(method, path, params, type)
44
+ RestClient.send(
45
+ method,
46
+ pw_url(path),
47
+ {params: params}.merge(common_params(type))
48
+ )
49
+ end
50
+
51
+ def self.pw_url(path)
52
+ "#{PassaporteWeb.configuration.url}#{path}"
53
+ end
54
+
55
+ def self.common_params(type)
56
+ {
57
+ authorization: (type == 'application' ? PassaporteWeb.configuration.application_credentials : PassaporteWeb.configuration.user_credentials),
58
+ content_type: :json,
59
+ accept: :json,
60
+ user_agent: PassaporteWeb.configuration.user_agent
61
+ }
62
+ end
63
+
64
+ def self.encoded_body(body)
65
+ body.is_a?(Hash) ? MultiJson.encode(body) : body
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,216 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+
4
+ # Represents an Identity on PassaporteWeb, i.e. a person. When you sign up for PassaporteWeb, your 'account'
5
+ # there is an Identity.
6
+ class Identity
7
+ include Attributable
8
+
9
+ ATTRIBUTES = [:accounts, :birth_date, :country, :cpf, :email, :first_name, :gender, :is_active, :language, :last_name, :nickname, :notifications, :send_myfreecomm_news, :send_partner_news, :services, :timezone, :update_info_url, :uuid, :password, :password2, :must_change_password, :inhibit_activation_message, :tos]
10
+ UPDATABLE_ATTRIBUTES = [:first_name, :last_name, :nickname, :cpf, :birth_date, :gender, :send_myfreecomm_news, :send_partner_news, :country, :language, :timezone]
11
+ CREATABLE_ATTRIBUTES = *(UPDATABLE_ATTRIBUTES + [:email, :password, :password2, :must_change_password, :tos])
12
+
13
+ attr_accessor *UPDATABLE_ATTRIBUTES
14
+ attr_reader *(ATTRIBUTES - UPDATABLE_ATTRIBUTES)
15
+ attr_reader :errors
16
+
17
+ # Finds an Identity by it's UUID. Returns the Identity instance with all fields set if successful.
18
+ # Raises a <tt>RestClient::ResourceNotFound</tt> exception if no Identity exists with the supplied
19
+ # UUID.
20
+ #
21
+ # If <tt>include_expired_accounts</tt> is passed as <tt>true</tt>, brings information about all
22
+ # accounts the Identity is related, regardless of the account's expiration date.
23
+ #
24
+ # If <tt>include_other_services</tt> is passed as <tt>true</tt>, brings information about accounts
25
+ # of all services the Identity is related to (not just the current logged in service / application).
26
+ #
27
+ # API method: <tt>/accounts/api/identities/:uuid/</tt>
28
+ #
29
+ # API documentation: https://app.passaporteweb.com.br/static/docs/usuarios.html#get-accounts-api-identities-uuid
30
+ def self.find(uuid, include_expired_accounts=false, include_other_services=false)
31
+ response = Http.get(
32
+ "/accounts/api/identities/#{uuid}/",
33
+ {include_expired_accounts: include_expired_accounts, include_other_services: include_other_services}
34
+ )
35
+ attributes_hash = MultiJson.decode(response.body)
36
+ load_identity(attributes_hash)
37
+ end
38
+
39
+ # Finds an Identity by it's email (emails are unique on PassaporteWeb). Returns the Identity instance
40
+ # with all fields set if successful. Raises a <tt>RestClient::ResourceNotFound</tt> exception if no
41
+ # Identity exists with the supplied email.
42
+ #
43
+ # If <tt>include_expired_accounts</tt> is passed as <tt>true</tt>, brings information about all
44
+ # accounts the Identity is related, regardless of the account's expiration date.
45
+ #
46
+ # If <tt>include_other_services</tt> is passed as <tt>true</tt>, brings information about accounts
47
+ # of all services the Identity is related to (not just the current logged in service / application).
48
+ #
49
+ # API method: <tt>GET /accounts/api/identities/?email=:email</tt>
50
+ #
51
+ # API documentation: https://app.passaporteweb.com.br/static/docs/usuarios.html#get-accounts-api-identities-email-email
52
+ def self.find_by_email(email, include_expired_accounts=false, include_other_services=false)
53
+ response = Http.get(
54
+ "/accounts/api/identities/",
55
+ {email: email, include_expired_accounts: include_expired_accounts, include_other_services: include_other_services}
56
+ )
57
+ attributes_hash = MultiJson.decode(response.body)
58
+ load_identity(attributes_hash)
59
+ end
60
+
61
+ # Checks if an Identity exists on PassaporteWeb and if the password is correct. Returns an instance of
62
+ # Identity for the supplied email if the password is correct (although with only basic attributes set).
63
+ # Returns <tt>false</tt> if the password is wrong or if no Identity exists on PassaporteWeb with
64
+ # the supplied email. Use it to validate that a user is who he says he is.
65
+ #
66
+ # API method: <tt>GET /accounts/api/auth/</tt>
67
+ #
68
+ # API documentation: https://app.passaporteweb.com.br/static/docs/usuarios.html#get-accounts-api-auth
69
+ def self.authenticate(email, password)
70
+ response = Http.custom_auth_get(
71
+ email,
72
+ password,
73
+ "/accounts/api/auth/"
74
+ )
75
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
76
+ attributes_hash = MultiJson.decode(response.body)
77
+ load_identity(attributes_hash)
78
+ rescue *[RestClient::Unauthorized] => e
79
+ false
80
+ end
81
+
82
+ # Checks if the supplied password is correct for the current Identity. Returns <tt>true</tt> if the
83
+ # password matches or <tt>false</tt> if the password is wrong. Use it to validate that a user is who
84
+ # he says he is.
85
+ #
86
+ # API method: <tt>GET /accounts/api/auth/</tt>
87
+ #
88
+ # API documentation: https://app.passaporteweb.com.br/static/docs/usuarios.html#get-accounts-api-auth
89
+ def authenticate(password)
90
+ raise ArgumentError, "email must be set" if (self.email.nil? || self.email.to_s.empty?)
91
+ response = Http.custom_auth_get(
92
+ self.email,
93
+ password,
94
+ "/accounts/api/auth/"
95
+ )
96
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
97
+ true
98
+ rescue *[RestClient::Unauthorized] => e
99
+ false
100
+ end
101
+
102
+ # Instanciates a new Identity with the supplied attributes. Only the attributes listed
103
+ # on <tt>Identity::CREATABLE_ATTRIBUTES</tt> are used when creating an Identity and
104
+ # on <tt>Identity::UPDATABLE_ATTRIBUTES</tt> are used when updating an Identity.
105
+ #
106
+ # Example:
107
+ #
108
+ # identity = PassaporteWeb::Identity.new(
109
+ # email: 'fulano@detal.com.br',
110
+ # password: '123456',
111
+ # password2: '123456',
112
+ # must_change_password: false,
113
+ # tos: true,
114
+ # first_name: 'Fulano',
115
+ # last_name: 'de Tal',
116
+ # nickname: 'Fulaninho',
117
+ # cpf: '342.766.570-40',
118
+ # birth_date: '1983-04-19',
119
+ # gender: 'M',
120
+ # send_myfreecomm_news: true,
121
+ # send_partner_news: false,
122
+ # country: 'Brasil',
123
+ # language: 'pt_BR',
124
+ # timezone: 'GMT-3'
125
+ # )
126
+ def initialize(attributes={})
127
+ set_attributes(attributes)
128
+ @errors = {}
129
+ end
130
+
131
+ # Returns a hash with all attribures of the identity.
132
+ def attributes
133
+ ATTRIBUTES.inject({}) do |hash, attribute|
134
+ hash[attribute] = self.send(attribute)
135
+ hash
136
+ end
137
+ end
138
+
139
+ # Compares one Identity with another, returns true if they have the same UUID.
140
+ def ==(other)
141
+ self.uuid == other.uuid
142
+ end
143
+
144
+ # Returns true if both Identity are the same object.
145
+ def ===(other)
146
+ self.object_id == other.object_id
147
+ end
148
+
149
+ # Saves the Identity on PassaporteWeb, creating it if new or updating it if existing. Returns true
150
+ # if successfull or false if not. In case of failure, it will fill the <tt>errors</tt> attribute
151
+ # with the reason for the failure to save the object.
152
+ #
153
+ # The attributes <tt>first_name</tt>, <tt>last_name</tt>, <tt>cpf</tt> and <tt>inhibit_activation_message</tt>
154
+ # are optional. <tt>password2</tt> and <tt>password</tt> fields are required even if the parameter
155
+ # <tt>must_change_password</tt> is used.
156
+ #
157
+ # API methods:
158
+ # * <tt>POST /accounts/api/create/</tt> (on create)
159
+ # * <tt>PUT /accounts/api/identities/:uuid/</tt> (on update)
160
+ #
161
+ # API documentation:
162
+ # * https://app.passaporteweb.com.br/static/docs/usuarios.html#post-accounts-api-create
163
+ # * https://app.passaporteweb.com.br/static/docs/usuarios.html#get-accounts-api-identities-email-email
164
+ #
165
+ # Example:
166
+ #
167
+ # identity = Identity.find_by_email('foo@bar.com')
168
+ # identity.save # => true
169
+ # identity.cpf = '12'
170
+ # identity.save # => false
171
+ # identity.errors # => {"cpf" => ["Certifique-se de que o valor tenha no mínimo 11 caracteres (ele possui 2)."]}
172
+ def save
173
+ # TODO validar atributos?
174
+ response = (persisted? ? update : create)
175
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
176
+ attributes_hash = MultiJson.decode(response.body)
177
+ set_attributes(attributes_hash)
178
+ @persisted = true
179
+ @errors = {}
180
+ true
181
+ rescue *[RestClient::Conflict, RestClient::BadRequest] => e
182
+ @errors = MultiJson.decode(e.response.body)
183
+ false
184
+ end
185
+
186
+ def persisted?
187
+ !self.uuid.nil? && @persisted == true
188
+ end
189
+
190
+ private
191
+
192
+ def self.load_identity(attributes)
193
+ identity = self.new(attributes)
194
+ identity.instance_variable_set(:@persisted, true)
195
+ identity
196
+ end
197
+
198
+ def create
199
+ Http.post("/accounts/api/create/", create_body)
200
+ end
201
+
202
+ def update
203
+ Http.put("/accounts/api/identities/#{self.uuid}/", update_body)
204
+ end
205
+
206
+ def update_body
207
+ self.attributes.select { |key, value| UPDATABLE_ATTRIBUTES.include?(key) && !value.nil? }
208
+ end
209
+
210
+ def create_body
211
+ self.attributes.select { |key, value| CREATABLE_ATTRIBUTES.include?(key) && !value.nil? }
212
+ end
213
+
214
+ end
215
+
216
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+
4
+ # Also represents a ServiceAccount, but uses diffetent API endpoints to list and create them
5
+ # from a existing Identity.
6
+ class IdentityServiceAccount
7
+ include Attributable
8
+
9
+ ATTRIBUTES = [:membership_details_url, :plan_slug, :roles, :url, :expiration, :service_data, :account_data, :add_member_url, :name, :uuid]
10
+ CREATABLE_ATTRIBUTES = [:plan_slug, :expiration, :name, :uuid]
11
+
12
+ attr_accessor *CREATABLE_ATTRIBUTES
13
+ attr_reader *(ATTRIBUTES - CREATABLE_ATTRIBUTES)
14
+ attr_reader :identity, :errors
15
+
16
+ # Finds all service accounts of the supplied Identity on the current authenticated application.
17
+ # Returns an array of IdentityServiceAccount.
18
+ #
19
+ # API method: <tt>GET /organizations/api/identities/:uuid/accounts/</tt>
20
+ #
21
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#get-organizations-api-identities-uuid-accounts
22
+ def self.find_all(identity, include_expired_accounts=false, role=nil)
23
+ params = {include_expired_accounts: include_expired_accounts}
24
+ params[:role] = role unless (role.nil? || role.to_s.empty?)
25
+ response = Http.get("/organizations/api/identities/#{identity.uuid}/accounts/", params)
26
+ raw_accounts = MultiJson.decode(response.body)
27
+ raw_accounts.map { |raw_account| load_identity_service_account(identity, raw_account) }
28
+ end
29
+
30
+ # Instanciates a new ServiceAccount to be created for the supplied Identity on the current
31
+ # authenticated application. See #save
32
+ def initialize(identity, attributes={})
33
+ set_attributes(attributes)
34
+ @identity = identity
35
+ @persisted = false
36
+ @errors = {}
37
+ end
38
+
39
+ # Creates a new ServiceAccount for the supplied Identity on the current authenticated application.
40
+ # The supplied Identity will be the ServiceAccount's owner. You should supply either the <tt>name</tt>
41
+ # or the (service account's) <tt>uuid</tt> attribute. If the latter is supplied, the supplied Identity
42
+ # must already be owner of at least one other ServiceAccount on the same group / organization.
43
+ #
44
+ # Returns true in case of success, false otherwise (along with failure reasons on #errors).
45
+ #
46
+ # API method: <tt>POST /organizations/api/identities/:uuid/accounts/</tt>
47
+ #
48
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#post-organizations-api-identities-uuid-accounts
49
+ def save
50
+ # TODO validar atributos?
51
+ response = Http.post("/organizations/api/identities/#{self.identity.uuid}/accounts/", create_body)
52
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 201
53
+ attributes_hash = MultiJson.decode(response.body)
54
+ set_attributes(attributes_hash)
55
+ @persisted = true
56
+ @errors = {}
57
+ true
58
+ rescue *[RestClient::BadRequest] => e
59
+ @persisted = false
60
+ @errors = MultiJson.decode(e.response.body)
61
+ false
62
+ end
63
+
64
+ # Returns true if the IdentityServiceAccount exists on PassaporteWeb
65
+ def persisted?
66
+ @persisted == true
67
+ end
68
+
69
+ # Returns a hash with all attribures of the IdentityServiceAccount
70
+ def attributes
71
+ ATTRIBUTES.inject({}) do |hash, attribute|
72
+ hash[attribute] = self.send(attribute)
73
+ hash
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def create_body
80
+ self.attributes.select { |key, value| CREATABLE_ATTRIBUTES.include?(key) && !value.nil? }
81
+ end
82
+
83
+ def self.load_identity_service_account(identity, attributes={})
84
+ isa = self.new(identity, attributes)
85
+ isa.instance_variable_set(:@persisted, true)
86
+ isa
87
+ end
88
+
89
+ end
90
+
91
+ end