soar-registry-staff 0.0.1

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: 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: []