soar-registry-staff 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d667963b213a8c1f8bfac707cd214818eb33e591
4
+ data.tar.gz: 3ce77a63e9ec45a7e84ecbef40891526c2557d3d
5
+ SHA512:
6
+ metadata.gz: 40202401ddea924599085587392e84cdfb10263a640e3980ddd6347cd4729d9bffac46013c9a5bbd502a405eacd9f07c5f7a388708a8a0a18aa42876c8f26643
7
+ data.tar.gz: b345267177081bba7e8b69d3b7cf1c79b3ee5ec9853617e9b62bf94b84f6c0a8a6a5f5cf18fe05f1ffa378af30cf08ba0ff13ba320a913b00d7ec48a573dfa1b
@@ -0,0 +1,132 @@
1
+ # Registry of staff identities
2
+ Allows you to query staff identities using the idm api.
3
+
4
+ *Please note:* Querying staff entities requires a different idr just for entities.
5
+
6
+ ## Quickstart
7
+
8
+ ### Required configuration
9
+ See config/config.yml
10
+ ```yaml
11
+ rule_set:
12
+ adaptor: 'Soar::Registry::Staff::Translator::DynamoDb'
13
+ provider:
14
+ adaptor: 'Soar::Registry::Staff::Directory::DynamoDb::Identity'
15
+ config:
16
+ table: 'identities'
17
+ region: 'us-west-2'
18
+ endpoint: 'http://localhost:8000'
19
+ credentials:
20
+ username: 'username'
21
+ password: 'secret'
22
+ ```
23
+
24
+ ### Create an instance of the staff identity registry
25
+ ```ruby
26
+ @idr = Soar::Registry::Staff::Identity.new(configuration)
27
+ ```
28
+
29
+ ### Getting a list of identifiers
30
+ ```ruby
31
+ > identifiers = @idr.get_identifiers( {"email" => "admin@hetzner.co.za"} )
32
+ > identifiers = @idr.get_identifiers( {"identity_id" => "identity-820d5660-2204-4f7d-8c04-746313439b81"} )
33
+ > puts identifiers.inspect
34
+ ["admin@hetzner.co.za", "identity-820d5660-2204-4f7d-8c04-746313439b81"]
35
+ ```
36
+
37
+ ### Getting a list of roles
38
+ ```ruby
39
+ > roles = @idr.get_roles( {"email" => "admin@hetzner.co.za"} )
40
+ > roles = @idr.get_roles( {"identity_id" => "identity-820d5660-2204-4f7d-8c04-746313439b81"} )
41
+ > puts roles.inspect
42
+ ["staff", "configuration_publisher", "configuration_consumer"]
43
+ ```
44
+
45
+ ### Getting a hash of attributes for a role
46
+ ```ruby
47
+ > role = 'staff'
48
+ > attributes = @idr.get_attributes( {"email" => "admin@hetzner.co.za"}, role)
49
+ > attributes = @idr.get_attributes( {"identity_id" => "identity-820d5660-2204-4f7d-8c04-746313439b81"}, role )
50
+ > puts attributes.inspect
51
+ {
52
+ "staff": {
53
+ "department": "technical"
54
+ }
55
+ }
56
+
57
+ ```
58
+
59
+ ### Getting a hash of all attributes
60
+ ```ruby
61
+ > attributes = @idr.get_attributes( {"email" => "admin@hetzner.co.za"} )
62
+ > attributes = @idr.get_attributes( {"identity_id" => "identity-820d5660-2204-4f7d-8c04-746313439b81"} )
63
+ > puts attributes.inspect
64
+ {
65
+ "identity_id" => "identity-820d5660-2204-4f7d-8c04-746313439b81",
66
+ "entity_id"=> "entity-bad85eb9-0713-4da7-8d36-07a8e4b00eab",
67
+ "email"=> "admin@hetzner.co.za",
68
+ "roles"=> {
69
+ "staff"=> {},
70
+ "configuration_publisher"=> {
71
+ "configuration_identifiers"=> ["*"]
72
+ },
73
+ "configuration_consumer"=> {
74
+ "configuration_identifiers"=> ["*"]
75
+ }
76
+ },
77
+ "address"=> {
78
+ "detail"=> "Belvedere Office Park, Unit F",
79
+ "street"=> "Bella Rosa Street",
80
+ "suburb"=> "Tygervalley",
81
+ "city"=> "Durbanville",
82
+ "postal"=> "7550"
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Tests
88
+
89
+ ### Quicktest
90
+ ```
91
+ $ docker-compose --file docker-compose.test.yml up --abort-on-container-exit --remove-orphans
92
+ ```
93
+
94
+ ### CI
95
+ *NB:* container_name in docker-compose.ci.yml corresponds with name parameter of docker ps command in below bash script.
96
+ ```bash
97
+ #!/bin/bash
98
+ docker-compose --file docker-compose.test.yml up --build --abort-on-container-exit --remove-orphans --force-recreate;
99
+ EXIT_CODE=$(docker ps -a -f "name=soar-registry-staff" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
100
+ exit $EXIT_CODE;
101
+ ```
102
+
103
+ ### Unit tests
104
+
105
+ #### Run dynamodb
106
+ ```
107
+ $ docker-compose up
108
+ ```
109
+
110
+ #### Open another terminal and run:
111
+ ```
112
+ $ sudo -H pip install awscli
113
+ $ aws configure
114
+ $ bundle
115
+ $ rspec
116
+ ```
117
+
118
+ ### Useful commands:
119
+ ```
120
+ $ aws dynamodb list-tables --endpoint-url http://localhost:8000
121
+ $ aws dynamodb delete-table --table-name identities --endpoint-url http://localhost:8000
122
+ ```
123
+
124
+ ## Resources
125
+ * [DynamoDBLocal](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html)
126
+ * [Multiple AWS Credentials](https://blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs)
127
+ * [AWS SDK for Ruby](http://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/GettingStarted.Ruby.html)
128
+
129
+ ## References
130
+ * [soar idm](https://github.hetzner.co.za/hetznerZA/soar_idm/blob/master/lib/soar_idm/soar_idm.rb)
131
+ * [Domain analysis](https://docs.google.com/a/hetzner.co.za/drawings/d/1vGdzjKPD3gzn1e0bsC4liFCyxY31Qjjxe3y41beVBzw/edit?usp=sharing)
132
+ * [staff idr](https://github.hetzner.co.za/hetznerZA/idr_staff/blob/master/idr_staff/lib/idr_staff/staff_idr.rb)
@@ -0,0 +1,86 @@
1
+ require 'soar_idm/directory_provider'
2
+ require 'aws-sdk'
3
+ require 'hashie'
4
+ require 'soar/registry/staff/directory/error'
5
+
6
+ module Soar
7
+ module Registry
8
+ module Staff
9
+ module Directory
10
+ module DynamoDb
11
+ class Base < SoarIdm::DirectoryProvider
12
+
13
+ attr_reader :configuration, :credentials, :client
14
+
15
+ ##
16
+ # @param [Hash] configuration for the directory provider
17
+ # @return [Nil]
18
+ # @raise [Soar::Registry::Staff::Directory::Error::BootstrapError] if required configuration is missing
19
+ ##
20
+ def bootstrap(configuration)
21
+ @configuration = Hashie.stringify_keys(configuration)
22
+ raise Soar::Registry::Staff::Directory::Error::BootstrapError, 'Missing region' if not @configuration.has_key?('region')
23
+ raise Soar::Registry::Staff::Directory::Error::BootstrapError, 'Missing endpoint' if not @configuration.has_key?('endpoint')
24
+ @table_name = @configuration.delete('table')
25
+ raise Soar::Registry::Staff::Directory::Error::BootstrapError, 'Missing table name' if not @table_name
26
+ end
27
+
28
+ ##
29
+ # @return [Bool] whether directory provider received minimum required configuration
30
+ ##
31
+ def bootstrapped?
32
+ @configuration.has_key?('region') and @configuration.has_key?('endpoint')
33
+ end
34
+
35
+ def uri
36
+ @configuration['endpoint']
37
+ end
38
+
39
+ def authenticate(credentials)
40
+ raise Soar::Registry::Staff::Directory::Error::AuthenticationError, 'missing username' if not credentials.key?('username')
41
+ raise Soar::Registry::Staff::Directory::Error::AuthenticationError, 'missing password' if not credentials.key?('password')
42
+ @credentials = {
43
+ "access_key_id" => credentials['username'],
44
+ "secret_access_key" => credentials['password']
45
+ }
46
+ end
47
+
48
+ def connect
49
+ begin
50
+ options = @configuration.dup
51
+ options['credentials'] = Aws::Credentials.new(@credentials['access_key_id'], @credentials['secret_access_key'])
52
+ @client = Aws::DynamoDB::Client.new(Hashie.symbolize_keys(options))
53
+ @client
54
+ rescue ArgumentError => e
55
+ raise Soar::Registry::Staff::Directory::Error::ConnectionError, e.message
56
+ rescue
57
+ raise Soar::Registry::Staff::Directory::Error::ConnectionError, 'error creating client'
58
+ end
59
+
60
+ end
61
+
62
+ def connected?
63
+ return @client.class.name == 'Aws::DynamoDB::Client'
64
+ end
65
+
66
+ def ready?
67
+ begin
68
+ return @client.list_tables.table_names.class.name == 'Array'
69
+ rescue Seahorse::Client::NetworkingError
70
+ return false
71
+ end
72
+ end
73
+
74
+ def put_identity(entity)
75
+ @client.put_item({
76
+ table_name: @table_name,
77
+ item: Hashie.symbolize_keys(entity)
78
+ })
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,83 @@
1
+ require 'soar_idm/directory_provider'
2
+ require 'soar/registry/staff/directory/dynamo_db/base'
3
+ require 'aws-sdk'
4
+ require 'hashie'
5
+
6
+ module Soar
7
+ module Registry
8
+ module Staff
9
+ module Directory
10
+ module DynamoDb
11
+ class Identity < Base
12
+
13
+ LIMIT = 10
14
+ INDEXED_ATTRIBUTES = ['email', 'entity_id']
15
+
16
+ ##
17
+ # @param [String] identity_id primary key of the identity
18
+ # @return [Hash] the identity
19
+ # @raise [Soar::Registry::Staff::Directory::DynamoDb::Error::UniqueIdentifierNotFoundError] if primary key not found
20
+ ##
21
+ def fetch_identity(identifier)
22
+ options = {
23
+ table_name: @table_name,
24
+ key: {
25
+ identity_id: identifier
26
+ }
27
+ }
28
+ identity = @client.get_item(options)
29
+ raise Soar::Registry::Staff::Directory::Error::UniqueIdentifierNotFoundError, 'Unable to find identity' if identity.item.nil?
30
+ identity.item
31
+ end
32
+
33
+ ##
34
+ # @param [String] identifier_attribute
35
+ # @param [String] identifier_value
36
+ # @return [Array] list of identities
37
+ # @raise [ArgumentError] if query or index is not specified
38
+ ##
39
+ def search_identities(identifier_attribute, identifier_value)
40
+ raise ArgumentError, "Attribute is required" if identifier_attribute.nil?
41
+ raise ArgumentError, 'Value is required' if identifier_value.nil?
42
+ options = {
43
+ table_name: @table_name,
44
+ select: 'ALL_ATTRIBUTES',
45
+ limit: LIMIT,
46
+ key_condition_expression: "#{identifier_attribute} = :value",
47
+ expression_attribute_values: {
48
+ ":value": identifier_value
49
+ }
50
+ }
51
+
52
+ options.merge!({index_name: "#{identifier_attribute}_index"}) if INDEXED_ATTRIBUTES.include?(identifier_attribute)
53
+ identity = @client.query(options)
54
+ identity.items.map { |item|
55
+ Hashie.stringify_keys(item)
56
+ }
57
+ end
58
+
59
+ ##
60
+ # @return [Array] a list of primary keys and global secondary indexes
61
+ ##
62
+ def indexed_attributes
63
+ resp = @client.describe_table({
64
+ table_name: @table_name
65
+ })
66
+ indexed_attributes = []
67
+ resp['table']['key_schema'].each { |key_schema|
68
+ indexed_attributes << key_schema['attribute_name']
69
+ }
70
+ resp['table']['global_secondary_indexes'].each { |index|
71
+ index['key_schema'].each { |key_schema|
72
+ indexed_attributes << key_schema['attribute_name']
73
+ }
74
+ }
75
+ indexed_attributes
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Soar
2
+ module Registry
3
+ module Staff
4
+ module Directory
5
+ module Error
6
+ class BootstrapError < StandardError; end;
7
+ class AuthenticationError < StandardError; end;
8
+ class CommunicationError < StandardError; end;
9
+ class ConnectionError < StandardError; end;
10
+ class NotReadyError < StandardError; end;
11
+ class UniqueIdentifierNotFoundError < StandardError; end;
12
+ class MissingIndexError < StandardError; end;
13
+ class MissingQueryError < StandardError; end;
14
+ class UniqueIdentifierNotFound < StandardError; end;
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,90 @@
1
+ require 'soar_idm/soar_idm'
2
+ require 'soar/registry/staff/translator/dynamo_db'
3
+ require 'soar/registry/staff/directory/dynamo_db/identity'
4
+
5
+ module Soar
6
+ module Registry
7
+ module Staff
8
+
9
+ class Identity < SoarIdm::IdmApi
10
+
11
+ attr_reader :directory
12
+ attr_reader :translator
13
+ attr_reader :client
14
+
15
+ ##
16
+ # @param [Hash] configuration
17
+ # for example see config/config.yml
18
+ ##
19
+ def initialize(configuration)
20
+ @translator = Object.const_get(configuration['rule_set']['adaptor']).new
21
+ @directory = Object::const_get(configuration['provider']['adaptor']).new
22
+ @directory.bootstrap(configuration['provider']['config'])
23
+ @directory.authenticate(configuration['provider']['credentials'])
24
+ @client = @directory.connect
25
+ raise Soar::Registry::Staff::Directory::Error::BootstrapError if not @directory.bootstrapped?
26
+ raise Soar::Registry::Staff::Directory::Error::ConnectionError if not @directory.connected?
27
+ raise Soar::Registry::Staff::Directory::Error::NotReadyError if not @directory.ready?
28
+ end
29
+
30
+ ##
31
+ # @param [String] identity_id
32
+ # @return [Array] list of roles
33
+ def calculate_roles(identity_id)
34
+ entry = @directory.fetch_identity(identity_id)
35
+ return nil if not entry
36
+ identity = @translator.get_identity(entry)
37
+ roles = []
38
+ identity['roles'].each do |role, attributes|
39
+ roles << role
40
+ end
41
+ roles
42
+ end
43
+
44
+ ##
45
+ # @param [String] identity_id
46
+ # @return [Array] list of identifiers
47
+ ##
48
+ def calculate_identifiers(identity_id)
49
+ indexes = @directory.indexed_attributes
50
+ entry = @directory.fetch_identity(identity_id)
51
+ identity = @translator.get_identity(entry)
52
+ identifiers = []
53
+ indexes.each { |index|
54
+ identifiers << identity[index]
55
+ }
56
+ identifiers
57
+ end
58
+
59
+ ##
60
+ # @param [String] identity_id
61
+ # @param [String] role
62
+ # @return [Hash] A hash of attributes
63
+ def calculate_attributes(identity_id, role)
64
+ entry = @directory.fetch_identity(identity_id)
65
+ return nil if not entry
66
+ identity = @translator.get_identity(entry)
67
+ { role => identity['roles'][role] }
68
+ end
69
+
70
+ ##
71
+ # @param [String] identity_id
72
+ # @return [Hash] Hash of attributes keyed by role
73
+ def calculate_all_attributes(identity_id)
74
+ entry = @directory.fetch_identity(identity_id)
75
+ @translator.get_identity(entry)
76
+ end
77
+
78
+ ##
79
+ # @param [Hash] identifier containing attribute name and attribute value
80
+ # @return [Array]
81
+ def calculate_identities(identifier)
82
+ identifier = @translator.get_identifier(identifier)
83
+ entries = @directory.search_identities(identifier.keys[0], identifier[identifier.keys[0]] )
84
+ [@translator.get_identity(entries)[0]['identity_id']]
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,34 @@
1
+ module Soar
2
+ module Registry
3
+ module Staff
4
+ module Translator
5
+ class DynamoDb
6
+
7
+ ##
8
+ # @param [Hash] entry a single entry from datasource
9
+ # @returns [Hash] identity a single identity
10
+ ##
11
+ def get_identity(entry)
12
+ return entry
13
+ end
14
+
15
+ ##
16
+ # @param [Array] entries a list of entries from data source
17
+ # @return [Array] identities a list of identities
18
+ ##
19
+ def get_identities(entries)
20
+ return entries
21
+ end
22
+
23
+ ##
24
+ # @param [String|Hash] identifier could be a json string or a ruby hash
25
+ # @return [Hash] identifier with identifying attribute key and attribute value
26
+ ##
27
+ def get_identifier(identifier)
28
+ identifier.is_a?(Hash) ? identifier : JSON.parse(identifier)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soar-registry-staff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Charles Mulder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: soar_idm
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.6'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.6.6
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.6'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.6.6
47
+ - !ruby/object:Gem::Dependency
48
+ name: aws-sdk-core
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.6'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.6.11
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '2.6'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.6.11
67
+ - !ruby/object:Gem::Dependency
68
+ name: hashie
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '3.4'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 3.4.6
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.4'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 3.4.6
87
+ description: Registry of staff identities and entities
88
+ email: charles.mulder@hetzner.co.za
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - README.md
94
+ - lib/soar/registry/staff/directory/dynamo_db/base.rb
95
+ - lib/soar/registry/staff/directory/dynamo_db/identity.rb
96
+ - lib/soar/registry/staff/directory/error.rb
97
+ - lib/soar/registry/staff/identity.rb
98
+ - lib/soar/registry/staff/translator/dynamo_db.rb
99
+ homepage: https://gitlab.host-h.net/registries/staff
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.5.1
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Staff Registry
123
+ test_files: []