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,118 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+
4
+ # Represents a ServiceAccount on PassaporteWeb, which is the 'account' of an Identity within a Service. A
5
+ # Service may have many ServiceAccount s and many Identity ies via it's ServiceAccount s. A Identity may
6
+ # belong to serveral Service s via it's ServiceAccount s.
7
+ class ServiceAccount
8
+ include Attributable
9
+
10
+ ATTRIBUTES = [:plan_slug, :expiration, :identity, :roles, :member_uuid, :role, :include_expired_accounts, :name, :members_data, :url, :service_data, :account_data, :add_member_url]
11
+ UPDATABLE_ATTRIBUTES = [:plan_slug, :expiration]
12
+
13
+ attr_accessor *UPDATABLE_ATTRIBUTES
14
+ attr_reader *(ATTRIBUTES - UPDATABLE_ATTRIBUTES)
15
+ attr_reader :errors
16
+
17
+ def initialize(attributes={})
18
+ set_attributes(attributes)
19
+ @errors = {}
20
+ end
21
+
22
+ def uuid
23
+ self.account_data['uuid'] if self.account_data
24
+ end
25
+
26
+ # Finds all ServiceAccounts that the current authenticated application has access to, paginated. By default finds
27
+ # 20 ServiceAccounts per request, starting at "page" 1. Returns an OpenStruct object with two attributes
28
+ # <tt>service_accounts</tt> and <tt>meta</tt>. <tt>service_accounts</tt> is an array of ServiceAccount instances or an empty array
29
+ # if no ServiceAccounts are found. <tt>meta</tt> is an OpenStruct object with information about limit and available
30
+ # pagination values, to use in subsequent calls to <tt>.find_all</tt>. Raises a
31
+ # <tt>RestClient::ResourceNotFound</tt> exception if the requested page does not exist.
32
+ #
33
+ # API method: <tt>GET /organizations/api/accounts/</tt>
34
+ #
35
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#get-organizations-api-accounts
36
+ #
37
+ # Example:
38
+ # data = PassaporteWeb::ServiceAccount.find_all
39
+ # data.service_accounts # => [account1, account2, ...]
40
+ # data.meta # => #<OpenStruct limit=20, next_page=2, prev_page=nil, first_page=1, last_page=123>
41
+ # data.meta.limit # => 20
42
+ # data.meta.next_page # => 2
43
+ # data.meta.prev_page # => nil
44
+ # data.meta.first_page # => 1
45
+ # data.meta.last_page # => 123
46
+ def self.find_all(page=1, limit=20)
47
+ response = Http.get("/organizations/api/accounts/?page=#{Integer(page)}&limit=#{Integer(limit)}")
48
+ raw_accounts = MultiJson.decode(response.body)
49
+ result_hash = {}
50
+ result_hash[:service_accounts] = raw_accounts.map { |raw_account| load_service_account(raw_account) }
51
+ result_hash[:meta] = PassaporteWeb::Helpers.meta_links_from_header(response.headers[:link])
52
+ PassaporteWeb::Helpers.convert_to_ostruct_recursive(result_hash)
53
+ end
54
+
55
+ # Instanciates an ServiceAccount identified by it's UUID, with all the details. Only service accounts related to the current
56
+ # authenticated application are available. Returns the ServiceAccount instance if successful, or raises a
57
+ # <tt>RestClient::ResourceNotFound</tt> exception if no ServiceAccount exists with that UUID (or if it is not
58
+ # related to the current authenticated application).
59
+ #
60
+ # API method: <tt>GET /organizations/api/accounts/:uuid/</tt>
61
+ #
62
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#get-organizations-api-accounts-uuid
63
+ def self.find(uuid)
64
+ response = Http.get("/organizations/api/accounts/#{uuid}/")
65
+ attributes_hash = MultiJson.decode(response.body)
66
+ load_service_account(attributes_hash)
67
+ end
68
+
69
+ # Updates an existing ServiceAccount, changing it's plan_slug and/or expiration date. Returns true
70
+ # if successfull or false if not. In case of failure, it will fill the <tt>errors</tt> attribute
71
+ # with the reason for the failure to save the object.
72
+ #
73
+ # API method: <tt>PUT /organizations/api/accounts/:uuid/</tt>
74
+ #
75
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#put-organizations-api-accounts-uuid
76
+ def save
77
+ # TODO validar atributos?
78
+ response = update
79
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
80
+ attributes_hash = MultiJson.decode(response.body)
81
+ set_attributes(attributes_hash)
82
+ @errors = {}
83
+ true
84
+ rescue *[RestClient::Conflict, RestClient::BadRequest] => e
85
+ @errors = MultiJson.decode(e.response.body)
86
+ false
87
+ end
88
+
89
+ def attributes
90
+ ATTRIBUTES.inject({}) do |hash, attribute|
91
+ hash[attribute] = self.send(attribute)
92
+ hash
93
+ end
94
+ end
95
+
96
+ def persisted?
97
+ @persisted == true
98
+ end
99
+
100
+ private
101
+
102
+ def update
103
+ Http.put("/organizations/api/accounts/#{self.uuid}/", update_body)
104
+ end
105
+
106
+ def update_body
107
+ self.attributes.select { |key, value| UPDATABLE_ATTRIBUTES.include?(key) && !value.nil? }
108
+ end
109
+
110
+ def self.load_service_account(attributes_hash)
111
+ service_account = self.new(attributes_hash)
112
+ service_account.instance_variable_set(:@persisted, true)
113
+ service_account
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,136 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+
4
+ # Represents the membership of an Identity within a ServiceAccount on PassaporteWeb.
5
+ class ServiceAccountMember
6
+
7
+ attr_accessor :roles
8
+ attr_reader :service_account, :identity
9
+ attr_reader :errors
10
+
11
+ # Instanciates a new ServiceAccountMember to represent the membership of the supplied Identity in
12
+ # the supplied ServiceAccount. The <tt>roles</tt> attribute should be an array of strings. Any
13
+ # value is accepted, only 'owner' is reserved. By default uses only the value of 'user'.
14
+ def initialize(service_account, identity, roles=['user'])
15
+ @service_account = service_account
16
+ @identity = identity
17
+ @roles = roles
18
+ @membership_details_url = nil
19
+ @errors = {}
20
+ end
21
+
22
+ # Finds the membership relation between the supplied Identity in the supplied ServiceAccount and returns
23
+ # an instance of ServiceAccountMember representing it.
24
+ #
25
+ # Raises a <tt>RestClient::ResourceNotFound</tt> exception if the supplied Identity does not have a
26
+ # membership within the supplied ServiceAccount, or if any of the supplied objects is invalid or
27
+ # not existent.
28
+ #
29
+ # API method: <tt>GET /organizations/api/accounts/:uuid/members/:member_uuid/</tt>
30
+ #
31
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#get-organizations-api-accounts-uuid-members-member-uuid
32
+ def self.find(service_account, identity)
33
+ response = Http.get("/organizations/api/accounts/#{service_account.uuid}/members/#{identity.uuid}/")
34
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
35
+ attributes_hash = MultiJson.decode(response.body)
36
+ member = self.new(service_account, identity, attributes_hash['roles'])
37
+ member.instance_variable_set(:@persisted, true)
38
+ member.instance_variable_set(:@destroyed, false)
39
+ member
40
+ end
41
+
42
+ # Returns true if the ServiceAccountMember was loaded from PassaporteWeb or saved there successfully.
43
+ def persisted?
44
+ @persisted == true
45
+ end
46
+
47
+ # Returns true if the ServiceAccountMember object has been destroyed (and thus represents a membership
48
+ # no more valid on PassaporteWeb)
49
+ def destroyed?
50
+ @destroyed == true
51
+ end
52
+
53
+ def membership_details_url # :nodoc:
54
+ return @membership_details_url if @membership_details_url
55
+ "/organizations/api/accounts/#{self.service_account.uuid}/members/#{self.identity.uuid}/" if persisted?
56
+ end
57
+
58
+ # Creates or updates the ServiceAccountMember object on PassaporteWeb. Returns true if successful and false
59
+ # (along with the reason for failure in the #errors method) otherwise.
60
+ #
61
+ # On update, the only attribute that can be changed is the <tt>roles</tt>. This can be set to anything,
62
+ # including 'owner', on update (on create, the 'owner' role is not allowed.)
63
+ #
64
+ # API methods:
65
+ # * <tt>POST /organizations/api/accounts/:uuid/members/</tt> (on create)
66
+ # * <tt>PUT /organizations/api/accounts/:uuid/members/:member_uuid/</tt> (on update)
67
+ #
68
+ # API documentation:
69
+ # * https://app.passaporteweb.com.br/static/docs/account_manager.html#post-organizations-api-accounts-uuid-members
70
+ # * https://app.passaporteweb.com.br/static/docs/account_manager.html#put-organizations-api-accounts-uuid-members-member-uuid
71
+ def save
72
+ self.persisted? ? update : create
73
+ end
74
+
75
+ # Destroys the membership relation between a Identity and a ServiceAccount, e.g. the Identity no longer will be a
76
+ # member of the ServiceAccount. Returns true if successful of false (along with the reason for failure in the #errors
77
+ # method) otherwise.
78
+ #
79
+ # If the member has a 'owner' role, than the membership can not be destroyed.
80
+ #
81
+ # API method: <tt>DELETE /organizations/api/accounts/:uuid/members/:member_uuid/</tt>
82
+ #
83
+ # API documentation: https://app.passaporteweb.com.br/static/docs/account_manager.html#delete-organizations-api-accounts-uuid-members-member-uuid
84
+ def destroy
85
+ return false unless self.persisted?
86
+ response = Http.delete(self.membership_details_url)
87
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 204
88
+ @errors = {}
89
+ @persisted = false
90
+ @destroyed = true
91
+ true
92
+ rescue *[RestClient::NotAcceptable] => e
93
+ @errors = MultiJson.decode(e.response.body)
94
+ @persisted = true
95
+ @destroyed = false
96
+ false
97
+ end
98
+
99
+ private
100
+
101
+ def create
102
+ response = Http.post(
103
+ "/organizations/api/accounts/#{self.service_account.uuid}/members/",
104
+ {identity: self.identity.uuid, roles: self.roles}
105
+ )
106
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200 # doc says 201
107
+ attributes_hash = MultiJson.decode(response.body)
108
+ @membership_details_url = attributes_hash['membership_details_url']
109
+ @errors = {}
110
+ @persisted = true
111
+ true
112
+ rescue *[RestClient::Conflict, RestClient::BadRequest, RestClient::NotAcceptable] => e
113
+ @errors = MultiJson.decode(e.response.body)
114
+ @persisted = false
115
+ false
116
+ end
117
+
118
+ def update
119
+ response = Http.put(
120
+ self.membership_details_url,
121
+ {roles: self.roles}
122
+ )
123
+ raise "unexpected response: #{response.code} - #{response.body}" unless response.code == 200
124
+ attributes_hash = MultiJson.decode(response.body)
125
+ @errors = {}
126
+ @persisted = true
127
+ true
128
+ rescue *[RestClient::BadRequest, RestClient::NotAcceptable] => e
129
+ @errors = MultiJson.decode(e.response.body)
130
+ @persisted = true
131
+ false
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module PassaporteWeb
3
+ VERSION = "0.0.10"
4
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ # Ensure we require the local version and not one we might have installed already
6
+ require File.join([File.dirname(__FILE__),'lib','passaporte_web','version.rb'])
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "passaporteweb-client"
10
+ spec.version = PassaporteWeb::VERSION
11
+ spec.authors = ["Rodrigo Tassinari de Oliveira", "Rodrigo Martins"]
12
+ spec.email = ["rodrigo@pittlandia.net", "rodrigo.tassinari@myfreecomm.com.br", "rodrigo.martins@myfreecomm.com.br", "rodrigo@rrmartins.com"]
13
+ spec.description = %q{A Ruby client for the PassaporteWeb REST API}
14
+ spec.summary = %q{A Ruby client for the PassaporteWeb REST API: https://app.passaporteweb.com.br/static/docs/}
15
+ spec.homepage = "https://github.com/myfreecomm/passaporteweb-client-ruby"
16
+ spec.license = "Apache-v2"
17
+ spec.has_rdoc = true
18
+
19
+ # VCR cassettes are too long for the gemspec, see http://stackoverflow.com/questions/14371686/building-rails-3-engine-throwing-gempackagetoolongfilename-error
20
+ # spec.files = `git ls-files`.split($/)
21
+ spec.files = `git ls-files`.split($/).reject { |f| f =~ %r{(vcr_cassettes)/} }
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "rest-client", "~> 1.6.7"
27
+ spec.add_dependency "multi_json", "~> 1.7.1"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.3.2"
30
+ spec.add_development_dependency "rake", "~> 10.0.4"
31
+ spec.add_development_dependency 'rdoc', '~> 4.0.1'
32
+ spec.add_development_dependency "rspec", "~> 2.13.0"
33
+ spec.add_development_dependency "vcr", "~> 2.4.0"
34
+ spec.add_development_dependency "webmock", "~> 1.9.3"
35
+ spec.add_development_dependency "pry", "~> 0.9.12"
36
+ spec.add_development_dependency "pry-nav", "~> 0.2.3"
37
+ spec.add_development_dependency "awesome_print", "~> 1.1.0"
38
+ spec.add_development_dependency "simplecov", "~> 0.7.1"
39
+ spec.add_development_dependency "coveralls", "~> 0.6.3"
40
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PassaporteWeb::Configuration do
5
+
6
+ it "should use the production PassaporteWeb URL by default" do
7
+ PassaporteWeb::Configuration.new.url.should == 'https://app.passaporteweb.com.br'
8
+ end
9
+
10
+ it "should use a default user agent" do
11
+ PassaporteWeb::Configuration.new.user_agent.should == "PassaporteWeb Ruby Client v#{PassaporteWeb::VERSION}"
12
+ end
13
+
14
+ it 'should allow setting the configuration parameters' do
15
+ config = PassaporteWeb::Configuration.new
16
+
17
+ config.url = 'http://sandbox.app.passaporteweb.com.br'
18
+ config.application_token = 'some-app-token'
19
+ config.application_secret = 'some-app-secret'
20
+
21
+ config.url.should == 'http://sandbox.app.passaporteweb.com.br'
22
+ config.application_token.should == 'some-app-token'
23
+ config.application_secret.should == 'some-app-secret'
24
+ config.user_token.should be_nil
25
+ end
26
+
27
+ describe "#application_credentials" do
28
+ let(:config) { PassaporteWeb::Configuration.new }
29
+ it "should return the HTTP Basic Auth header value for the application login" do
30
+ config.application_token = 'some-app-token'
31
+ config.application_secret = 'some-app-secret'
32
+ config.application_credentials.should == 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0'
33
+ end
34
+ it "should require the application_token to be set" do
35
+ config.application_secret = 'some-app-secret'
36
+ expect { config.application_credentials }.to raise_error(ArgumentError, 'application_token not set')
37
+ end
38
+ it "should require the application_secret to be set" do
39
+ config.application_token = 'some-app-token'
40
+ expect { config.application_credentials }.to raise_error(ArgumentError, 'application_secret not set')
41
+ end
42
+ end
43
+
44
+ describe "#user_credentials" do
45
+ let(:config) { PassaporteWeb::Configuration.new }
46
+ it "should return the HTTP Basic Auth header value for the user login" do
47
+ config.user_token = 'some-user-token'
48
+ config.user_credentials.should == 'Basic OnNvbWUtdXNlci10b2tlbg=='
49
+ end
50
+ it "should require the user_token to be set" do
51
+ expect { config.user_credentials }.to raise_error(ArgumentError, 'user_token not set')
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PassaporteWeb::Helpers do
5
+
6
+ describe ".meta_links_from_header" do
7
+ it "should return a hash from the link header string" do
8
+ 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=123&limit=3>; rel=last, <http://sandbox.app.passaporteweb.com.br/organizations/api/accounts/?page=1&limit=3>; rel=first"
9
+ described_class.meta_links_from_header(link_header).should == {
10
+ limit: 3,
11
+ next_page: 3,
12
+ prev_page: 1,
13
+ first_page: 1,
14
+ last_page: 123
15
+ }
16
+ end
17
+ end
18
+
19
+ describe ".convert_to_ostruct_recursive" do
20
+ it "should convert a hash recursevely to a OpenStruct" do
21
+ hash = {a: 1, b: {c: 3, d: 4, e: {f: 6, g: 7}}}
22
+ os = described_class.convert_to_ostruct_recursive(hash)
23
+ os.should be_instance_of(OpenStruct)
24
+ os.a.should == 1
25
+ os.b.should be_instance_of(OpenStruct)
26
+ os.b.c.should == 3
27
+ os.b.d.should == 4
28
+ os.b.e.should be_instance_of(OpenStruct)
29
+ os.b.e.f.should == 6
30
+ os.b.e.g.should == 7
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PassaporteWeb::Http do
5
+
6
+ before(:each) do
7
+ PassaporteWeb.configure do |c|
8
+ c.url = 'https://some/where'
9
+ c.user_agent = 'My Mocking App v1.1'
10
+ c.application_token = 'some-app-token'
11
+ c.application_secret = 'some-app-secret'
12
+ c.user_token = 'some-user-token'
13
+ end
14
+ end
15
+
16
+ let(:mock_response) { mock('restclient http response') }
17
+
18
+ describe ".get" do
19
+ it "should use RestClient.get with the supplied params and common options" do
20
+ RestClient.should_receive(:get).with(
21
+ 'https://some/where/foo',
22
+ params: {spam: 'eggs'},
23
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
24
+ content_type: :json,
25
+ accept: :json,
26
+ user_agent: 'My Mocking App v1.1'
27
+ ).and_return(mock_response)
28
+ described_class.get('/foo', spam: 'eggs')
29
+ end
30
+ end
31
+
32
+ describe ".custom_auth_get" do
33
+ it "should use RestClient.get with the supplied params and common options, but with the custom authorization" do
34
+ RestClient.should_receive(:get).with(
35
+ 'https://some/where/foo',
36
+ params: {spam: 'eggs'},
37
+ authorization: 'Basic am9obkBkb2UuY29tOnRoZWJpcmRpc3RoZXdvcmQ=',
38
+ content_type: :json,
39
+ accept: :json,
40
+ user_agent: 'My Mocking App v1.1'
41
+ ).and_return(mock_response)
42
+ described_class.custom_auth_get('john@doe.com', 'thebirdistheword', '/foo', spam: 'eggs')
43
+ end
44
+ end
45
+
46
+ describe ".delete" do
47
+ it "should use RestClient.delete with the supplied params and common options" do
48
+ RestClient.should_receive(:delete).with(
49
+ 'https://some/where/foo',
50
+ params: {spam: 'eggs'},
51
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
52
+ content_type: :json,
53
+ accept: :json,
54
+ user_agent: 'My Mocking App v1.1'
55
+ ).and_return(mock_response)
56
+ described_class.delete('/foo', spam: 'eggs')
57
+ end
58
+ end
59
+
60
+ describe ".put" do
61
+ it "should use RestClient.get with the supplied params and common options, encoding body as json" do
62
+ RestClient.should_receive(:put).with(
63
+ 'https://some/where/foo',
64
+ '{"hello":"world"}',
65
+ params: {spam: 'eggs'},
66
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
67
+ content_type: :json,
68
+ accept: :json,
69
+ user_agent: 'My Mocking App v1.1'
70
+ ).and_return(mock_response)
71
+ described_class.put('/foo', {hello: 'world'}, {spam: 'eggs'})
72
+ end
73
+ it "should use RestClient.get with the supplied params and common options, with body already as json" do
74
+ RestClient.should_receive(:put).with(
75
+ 'https://some/where/foo',
76
+ '{"hello":"world"}',
77
+ params: {spam: 'eggs'},
78
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
79
+ content_type: :json,
80
+ accept: :json,
81
+ user_agent: 'My Mocking App v1.1'
82
+ ).and_return(mock_response)
83
+ described_class.put('/foo', '{"hello":"world"}', {spam: 'eggs'})
84
+ end
85
+ end
86
+
87
+ describe ".post" do
88
+ it "should use RestClient.post with the supplied params and common options, encoding body as json" do
89
+ RestClient.should_receive(:post).with(
90
+ 'https://some/where/foo',
91
+ '{"hello":"world"}',
92
+ params: {},
93
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
94
+ content_type: :json,
95
+ accept: :json,
96
+ user_agent: 'My Mocking App v1.1'
97
+ ).and_return(mock_response)
98
+ described_class.post('/foo', {hello: 'world'})
99
+ end
100
+ it "should use RestClient.post with the supplied params and common options, with body already as json" do
101
+ RestClient.should_receive(:post).with(
102
+ 'https://some/where/foo',
103
+ '{"hello":"world"}',
104
+ params: {},
105
+ authorization: 'Basic c29tZS1hcHAtdG9rZW46c29tZS1hcHAtc2VjcmV0',
106
+ content_type: :json,
107
+ accept: :json,
108
+ user_agent: 'My Mocking App v1.1'
109
+ ).and_return(mock_response)
110
+ described_class.post('/foo', '{"hello":"world"}')
111
+ end
112
+ end
113
+
114
+ end