passaporteweb-client 0.0.10

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,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