soar-authentication-identity_uuid_translator 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemspec +16 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/Dockerfile.dynamo_db +5 -0
- data/Dockerfile.features +7 -0
- data/Dockerfile.rspec +6 -0
- data/Gemfile +10 -0
- data/README.md +86 -0
- data/Rakefile +14 -0
- data/config/config.ci.dynamo_db.yml +15 -0
- data/config/config.ci.ldap.yml +17 -0
- data/config/config.ci.mysql.yml +18 -0
- data/config/config.dynamo_db.yml +15 -0
- data/config/config.ldap.yml +17 -0
- data/config/config.mysql.yml +18 -0
- data/docker-compose.ci.customer_client_number.yml +34 -0
- data/docker-compose.ci.customer_email.yml +34 -0
- data/docker-compose.ci.factory.yml +16 -0
- data/docker-compose.ci.role_generator.yml +27 -0
- data/docker-compose.ci.staff.yml +37 -0
- data/docker-compose.customer.yml +18 -0
- data/docker-compose.dynamo_db.yml +8 -0
- data/docker-compose.staff.yml +21 -0
- data/lib/soar/authentication/identity_uuid_translator.rb +13 -0
- data/lib/soar/authentication/identity_uuid_translator/error.rb +11 -0
- data/lib/soar/authentication/identity_uuid_translator/factory.rb +23 -0
- data/lib/soar/authentication/identity_uuid_translator/model.rb +24 -0
- data/lib/soar/authentication/identity_uuid_translator/provider/customer.rb +54 -0
- data/lib/soar/authentication/identity_uuid_translator/provider/staff.rb +33 -0
- data/lib/soar/authentication/identity_uuid_translator/role_generator.rb +21 -0
- data/lib/soar/authentication/identity_uuid_translator/test/fixtures/client_table.sql +91 -0
- data/lib/soar/authentication/identity_uuid_translator/test/fixtures/roles_table.json +27 -0
- data/lib/soar/authentication/identity_uuid_translator/test/fixtures/staff.json +18 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestration_provider/base.rb +78 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestration_provider/customer.rb +50 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestration_provider/customer_client_number.rb +52 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestration_provider/customer_email.rb +52 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestration_provider/staff.rb +79 -0
- data/lib/soar/authentication/identity_uuid_translator/test/orchestrator.rb +55 -0
- data/lib/soar/authentication/identity_uuid_translator/uuid_generator.rb +13 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 75d5f9b8860d8bdbdf32ffa7ffa5a3ca1876dec4
|
4
|
+
data.tar.gz: bf14f64cbb3fac590c1e4e1a7c8258512ae3db14
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0a41effcb26eb22ee5d79476637ef5ff61484ac150f52aa40adb4a05ec4f7badb3e9a3cb7b6973c58cf49bcffd6f823ee6c6297f84031dce3307dbad75d79c5
|
7
|
+
data.tar.gz: 040fb1c8e57b7c4b217a07602b726d96829163e598e74dc106a49ebceb5befe025e5ea748400d9534e10c2af127e9fd2c12bb7fac6752808bd383bf8628649a0
|
data/.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "soar-authentication-identity_uuid_translator"
|
3
|
+
spec.version = "1.0.0"
|
4
|
+
spec.authors = ["Charles Mulder"]
|
5
|
+
spec.email = ["charles.mulder@hetzner.co.za"]
|
6
|
+
|
7
|
+
spec.summary = %q{ Translate an identifier into a UUID}
|
8
|
+
spec.homepage = "https://github.com/hetznerZA/soar-authentication-identity"
|
9
|
+
|
10
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
11
|
+
spec.require_paths = ["lib"]
|
12
|
+
|
13
|
+
spec.add_runtime_dependency 'soar-registry-identity', '~> 4.0', '>= 4.0.2'
|
14
|
+
spec.add_runtime_dependency 'object_selector', '~> 1.0', '>= 1.0.1'
|
15
|
+
spec.add_runtime_dependency 'uuidtools', '~> 2.1', '>= 2.1.5'
|
16
|
+
end
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.swp
|
2
|
+
*.swo
|
3
|
+
# Used by dotenv library to load environment variables.
|
4
|
+
# .env
|
5
|
+
|
6
|
+
## Documentation cache and generated files:
|
7
|
+
/.yardoc/
|
8
|
+
/_yardoc/
|
9
|
+
/doc/
|
10
|
+
/rdoc/
|
11
|
+
|
12
|
+
## Environment normalization:
|
13
|
+
/.bundle/
|
14
|
+
/vendor/bundle
|
15
|
+
/lib/bundler/man/
|
16
|
+
|
17
|
+
# for a library or gem, you might want to ignore these files since the code is
|
18
|
+
# intended to run in multiple environments; otherwise, check them in:
|
19
|
+
# Gemfile.lock
|
20
|
+
.ruby-version
|
21
|
+
.ruby-gemset
|
22
|
+
*.gem
|
23
|
+
.byebug_history
|
24
|
+
Gemfile.lock
|
data/.rspec
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
FROM openjdk:7
|
2
|
+
WORKDIR /opt/dynamodb_local
|
3
|
+
RUN wget -q -O - http://dynamodb-local.s3-website-us-west-2.amazonaws.com/dynamodb_local_latest.tar.gz | tar -xzf - -C /opt/dynamodb_local
|
4
|
+
EXPOSE 8000
|
5
|
+
ENTRYPOINT ["/usr/bin/java", "-Djava.library.path=/opt/dynamodb_local/DynamoDBLocal_lib", "-jar", "DynamoDBLocal.jar", "-inMemory"]
|
data/Dockerfile.features
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
FROM ruby:2.3
|
2
|
+
RUN apt-get update && apt-get install -y mysql-client libmysqlclient-dev
|
3
|
+
WORKDIR /usr/local/src
|
4
|
+
ADD Gemfile .gemspec /usr/local/src/
|
5
|
+
RUN bundle install --without development --with test
|
6
|
+
ADD . /usr/local/src/
|
7
|
+
CMD sleep 10; bundle exec cucumber;
|
data/Dockerfile.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Soar::Authentication::IdentityUuidTranslator
|
2
|
+
Used by authentication service to translate an identity identifier into an UUID
|
3
|
+
|
4
|
+
## Domain Analysis
|
5
|
+
* [Identity UUID translation service](https://docs.google.com/a/hetzner.co.za/drawings/d/1lMExQnQn8uUyZ1eOjiDNrFr0OYZlKqkC-mx7oMx3sdo/edit?usp=sharing)
|
6
|
+
* [Staff UUID translator](https://docs.google.com/a/hetzner.co.za/drawings/d/1kD4-24cucI23Xo0O3VCN9gFpfTcg_QvYmNhCP8oLCVs/edit?usp=sharing)
|
7
|
+
* [Customer UUID translator](https://docs.google.com/a/hetzner.co.za/drawings/d/1wOINf-wbShfQbtv8p6py1y_IfxHtxjk93fA26d-D18M/edit?usp=sharing)
|
8
|
+
|
9
|
+
## Tests
|
10
|
+
|
11
|
+
### Local
|
12
|
+
|
13
|
+
#### Soar::Authentication::IdentityUuidTranslator::Factory
|
14
|
+
```bash
|
15
|
+
$ bundle exec rspec spec/factory_spec.rb
|
16
|
+
```
|
17
|
+
|
18
|
+
#### Soar::Authentication::IdentityUuidTranslator::RoleGenerator
|
19
|
+
```bash
|
20
|
+
$ docker-compose --file docker-compose.dynamo_db.yml up --remove-orphans
|
21
|
+
$ ROLES_DIRECTORY_CONFIG_FILE=config.dynamo_db.yml bundle exec rspec spec/role_generator_spec.rb
|
22
|
+
```
|
23
|
+
|
24
|
+
#### Soar::Authentication::IdentityUuidTranslator::Provider::Customer
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ docker-compose --file docker-compose.customer.yml up --remove-orphans
|
28
|
+
$ ROLES_DIRECTORY_CONFIG_FILE=config.dynamo_db.yml IDENTITY_DIRECTORY_CONFIG_FILE=config.mysql.yml TEST_ORCHESTRATION_PROVIDER=CustomerClientNumber bundle exec cucumber
|
29
|
+
```
|
30
|
+
|
31
|
+
```bash
|
32
|
+
$ docker-compose --file docker-compose.customer.yml up --remove-orphans
|
33
|
+
$ ROLES_DIRECTORY_CONFIG_FILE=config.dynamo_db.yml IDENTITY_DIRECTORY_CONFIG_FILE=config.mysql.yml TEST_ORCHESTRATION_PROVIDER=CustomerEmail bundle exec cucumber
|
34
|
+
```
|
35
|
+
|
36
|
+
#### Soar::Authentication::IdentityUuidTranslator::Provider::Staff
|
37
|
+
|
38
|
+
```bash
|
39
|
+
$ docker-compose --file docker-compose.staff.yml up --remove-orphans
|
40
|
+
$ ROLES_DIRECTORY_CONFIG_FILE=config.dynamo_db.yml IDENTITY_DIRECTORY_CONFIG_FILE=config.ldap.yml TEST_ORCHESTRATION_PROVIDER=Staff bundle exec cucumber
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Domain identity provider
|
44
|
+
Not implemented
|
45
|
+
|
46
|
+
### CI
|
47
|
+
|
48
|
+
#### Soar::Authentication::IdentityUuidTranslator::Factory
|
49
|
+
```bash
|
50
|
+
docker-compose --file docker-compose.ci.factory.yml --project-name soar-authentication-identity_uuid_translator-factory up --abort-on-container-exit --remove-orphans --build --force-recreate
|
51
|
+
EXIT_CODE=$(docker ps -a -f "name=soarauthenticationidentityuuidtranslatorfactory_tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
|
52
|
+
docker-compose --file docker-compose.ci.factory.yml --project-name soar-authentication-identity_uuid_translator-factory down --rmi local
|
53
|
+
exit $EXIT_CODE;
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Soar::Authentication::IdentityUuidTranslator::RoleGenerator
|
57
|
+
```bash
|
58
|
+
docker-compose --file docker-compose.ci.role_generator.yml --project-name soar-authentication-identity_uuid_translator-role_generator up --abort-on-container-exit --remove-orphans --build --force-recreate
|
59
|
+
EXIT_CODE=$(docker ps -a -f "name=soarauthenticationidentityuuidtranslatorrolegenerator_tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
|
60
|
+
docker-compose --file docker-compose.ci.role_generator.yml --project-name soar-authentication-identity_uuid_translator-role_generator down --rmi local
|
61
|
+
exit $EXIT_CODE;
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Soar::Authentication::IdentityUuidTranslator::Provider::Customer
|
65
|
+
```bash
|
66
|
+
docker-compose --file docker-compose.ci.customer_email.yml --project-name soar-authentication-identity_uuid_translator-provider-customer_email up --abort-on-container-exit --remove-orphans --build --force-recreate
|
67
|
+
EXIT_CODE=$(docker ps -a -f "name=soarauthenticationidentityuuidtranslatorprovidercustomeremail_tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
|
68
|
+
docker-compose --file docker-compose.ci.customer_email.yml --project-name soar-authentication-identity_uuid_translator-provider-customer_email down --rmi local
|
69
|
+
exit $EXIT_CODE;
|
70
|
+
```
|
71
|
+
|
72
|
+
```bash
|
73
|
+
docker-compose --file docker-compose.ci.customer_client_number.yml --project-name soar-authentication-identity_uuid_translator-provider-customer_client_number up --abort-on-container-exit --remove-orphans --build --force-recreate
|
74
|
+
EXIT_CODE=$(docker ps -a -f "name=soarauthenticationidentityuuidtranslatorprovidercustomerclientnumber_tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
|
75
|
+
docker-compose --file docker-compose.ci.customer_client_number.yml --project-name soar-authentication-identity_uuid_translator-provider-customer_client_number down --rmi local
|
76
|
+
exit $EXIT_CODE;
|
77
|
+
```
|
78
|
+
|
79
|
+
#### Soar::Authentication::IdentityUuidTranslator::Provider::Staff
|
80
|
+
```bash
|
81
|
+
docker-compose --file docker-compose.ci.staff.yml --project-name soar-authentication-identity_uuid_translator-provider-staff up --abort-on-container-exit --remove-orphans --build --force-recreate
|
82
|
+
EXIT_CODE=$(docker ps -a -f "name=soarauthenticationidentityuuidtranslatorproviderstaff_tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}");
|
83
|
+
docker-compose --file docker-compose.ci.staff.yml --project-name soar-authentication-identity_uuid_translator-provider-staff down --rmi local
|
84
|
+
exit $EXIT_CODE;
|
85
|
+
```
|
86
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class: 'Soar::Registry::Staff::Directory::DynamoDb'
|
2
|
+
config:
|
3
|
+
table:
|
4
|
+
name: 'identity_roles'
|
5
|
+
index:
|
6
|
+
partition_key: 'identity_uuid'
|
7
|
+
sort_key: 'identity_role'
|
8
|
+
configuration:
|
9
|
+
region: 'us-west-2'
|
10
|
+
endpoint: 'http://dynamodb:8000'
|
11
|
+
http_read_timeout: 1,
|
12
|
+
retry_limit: 0
|
13
|
+
credentials:
|
14
|
+
username: 'username'
|
15
|
+
password: 'secret'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class: 'Soar::Registry::Directory::Provider::Ldap'
|
2
|
+
config:
|
3
|
+
base: 'dc=hetzner,dc=co,dc=za'
|
4
|
+
index:
|
5
|
+
- 'entryuuid'
|
6
|
+
- 'mail'
|
7
|
+
config:
|
8
|
+
host: 'ldap'
|
9
|
+
port: 636
|
10
|
+
attributes:
|
11
|
+
- 'entryuuid'
|
12
|
+
- 'cn'
|
13
|
+
- 'mail'
|
14
|
+
- 'sn'
|
15
|
+
credentials:
|
16
|
+
username: 'cn=admin,dc=hetzner,dc=co,dc=za'
|
17
|
+
password: 'secret'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class: 'Soar::Registry::Directory::Provider::Mysql'
|
2
|
+
config:
|
3
|
+
index:
|
4
|
+
- "ID"
|
5
|
+
- "Client_Number"
|
6
|
+
- "Notifyemail_Invoice"
|
7
|
+
config:
|
8
|
+
database: 'konsoleh_genie'
|
9
|
+
table: 'Client'
|
10
|
+
host: 'mysql'
|
11
|
+
port: 3306
|
12
|
+
attributes:
|
13
|
+
- 'ID'
|
14
|
+
- 'Client_Number'
|
15
|
+
- 'Notifyemail_Invoice'
|
16
|
+
credentials:
|
17
|
+
username: 'genie'
|
18
|
+
password: 'secret'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class: 'Soar::Registry::Staff::Directory::DynamoDb'
|
2
|
+
config:
|
3
|
+
table:
|
4
|
+
name: 'identity_roles'
|
5
|
+
index:
|
6
|
+
partition_key: 'identity_uuid'
|
7
|
+
sort_key: 'identity_role'
|
8
|
+
configuration:
|
9
|
+
region: 'us-west-2'
|
10
|
+
endpoint: 'http://localhost:8000'
|
11
|
+
http_read_timeout: 1,
|
12
|
+
retry_limit: 0
|
13
|
+
credentials:
|
14
|
+
username: 'username'
|
15
|
+
password: 'secret'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class: 'Soar::Registry::Directory::Provider::Ldap'
|
2
|
+
config:
|
3
|
+
base: 'dc=hetzner,dc=co,dc=za'
|
4
|
+
index:
|
5
|
+
- 'entryuuid'
|
6
|
+
- 'mail'
|
7
|
+
config:
|
8
|
+
host: 'localhost'
|
9
|
+
port: 636
|
10
|
+
attributes:
|
11
|
+
- 'entryuuid'
|
12
|
+
- 'cn'
|
13
|
+
- 'mail'
|
14
|
+
- 'sn'
|
15
|
+
credentials:
|
16
|
+
username: 'cn=admin,dc=hetzner,dc=co,dc=za'
|
17
|
+
password: 'secret'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class: 'Soar::Registry::Directory::Provider::Mysql'
|
2
|
+
config:
|
3
|
+
index:
|
4
|
+
- "ID"
|
5
|
+
- "Client_Number"
|
6
|
+
- "Notifyemail_Invoice"
|
7
|
+
config:
|
8
|
+
database: 'konsoleh_genie'
|
9
|
+
table: 'Client'
|
10
|
+
host: '0.0.0.0'
|
11
|
+
port: 3306
|
12
|
+
attributes:
|
13
|
+
- 'ID'
|
14
|
+
- 'Client_Number'
|
15
|
+
- 'Notifyemail_Invoice'
|
16
|
+
credentials:
|
17
|
+
username: 'genie'
|
18
|
+
password: 'secret'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
version: "2"
|
2
|
+
services:
|
3
|
+
mysql:
|
4
|
+
image: mysql:5.5
|
5
|
+
expose:
|
6
|
+
- "3306"
|
7
|
+
environment:
|
8
|
+
- MYSQL_ROOT_PASSWORD=secret
|
9
|
+
- MYSQL_DATABASE=konsoleh_genie
|
10
|
+
- MYSQL_USER=genie
|
11
|
+
- MYSQL_PASSWORD=secret
|
12
|
+
|
13
|
+
dynamodb:
|
14
|
+
build:
|
15
|
+
context: .
|
16
|
+
dockerfile: Dockerfile.dynamo_db
|
17
|
+
expose:
|
18
|
+
- "8000"
|
19
|
+
|
20
|
+
tests:
|
21
|
+
build:
|
22
|
+
context: .
|
23
|
+
dockerfile: Dockerfile.features
|
24
|
+
links:
|
25
|
+
- mysql
|
26
|
+
- dynamodb
|
27
|
+
environment:
|
28
|
+
- ROLES_DIRECTORY_CONFIG_FILE=config.ci.dynamo_db.yml
|
29
|
+
- IDENTITY_DIRECTORY_CONFIG_FILE=config.ci.mysql.yml
|
30
|
+
- TEST_ORCHESTRATION_PROVIDER=CustomerClientNumber
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
version: "2"
|
2
|
+
services:
|
3
|
+
mysql:
|
4
|
+
image: mysql:5.5
|
5
|
+
expose:
|
6
|
+
- "3306"
|
7
|
+
environment:
|
8
|
+
- MYSQL_ROOT_PASSWORD=secret
|
9
|
+
- MYSQL_DATABASE=konsoleh_genie
|
10
|
+
- MYSQL_USER=genie
|
11
|
+
- MYSQL_PASSWORD=secret
|
12
|
+
|
13
|
+
dynamodb:
|
14
|
+
build:
|
15
|
+
context: .
|
16
|
+
dockerfile: Dockerfile.dynamo_db
|
17
|
+
expose:
|
18
|
+
- "8000"
|
19
|
+
|
20
|
+
tests:
|
21
|
+
build:
|
22
|
+
context: .
|
23
|
+
dockerfile: Dockerfile.features
|
24
|
+
links:
|
25
|
+
- mysql
|
26
|
+
- dynamodb
|
27
|
+
environment:
|
28
|
+
- ROLES_DIRECTORY_CONFIG_FILE=config.ci.dynamo_db.yml
|
29
|
+
- IDENTITY_DIRECTORY_CONFIG_FILE=config.ci.mysql.yml
|
30
|
+
- TEST_ORCHESTRATION_PROVIDER=CustomerEmail
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
version: "2"
|
2
|
+
services:
|
3
|
+
|
4
|
+
dynamodb:
|
5
|
+
build:
|
6
|
+
context: .
|
7
|
+
dockerfile: Dockerfile.dynamo_db
|
8
|
+
expose:
|
9
|
+
- "8000"
|
10
|
+
|
11
|
+
tests:
|
12
|
+
build:
|
13
|
+
context: .
|
14
|
+
dockerfile: Dockerfile.rspec
|
15
|
+
links:
|
16
|
+
- dynamodb
|
17
|
+
command:
|
18
|
+
- bundle
|
19
|
+
- exec
|
20
|
+
- rspec
|
21
|
+
- "spec/role_generator_spec.rb"
|
22
|
+
environment:
|
23
|
+
- ROLES_DIRECTORY_CONFIG_FILE=config.ci.dynamo_db.yml
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
version: "2"
|
2
|
+
services:
|
3
|
+
ldap:
|
4
|
+
image: osixia/openldap:1.1.7
|
5
|
+
expose:
|
6
|
+
- "636"
|
7
|
+
hostname: hetzner.co.za
|
8
|
+
environment:
|
9
|
+
- LDAP_DOMAIN=hetzner.co.za
|
10
|
+
- LDAP_ORGANISATION=Hetzner
|
11
|
+
- LDAP_ADMIN_PASSWORD=secret
|
12
|
+
- LDAP_TLS_VERIFY_CLIENT=never
|
13
|
+
- LDAP_TLS_PROTOCOL_MIN=1.2
|
14
|
+
- LDAP_TLS_CIPHER_SUITE=SECURE128:-VERS-SSL3.0:+VERS-TLS1.2
|
15
|
+
|
16
|
+
dynamodb:
|
17
|
+
build:
|
18
|
+
context: .
|
19
|
+
dockerfile: Dockerfile.dynamo_db
|
20
|
+
expose:
|
21
|
+
- "8000"
|
22
|
+
|
23
|
+
tests:
|
24
|
+
build:
|
25
|
+
context: .
|
26
|
+
dockerfile: Dockerfile.features
|
27
|
+
links:
|
28
|
+
- ldap
|
29
|
+
- dynamodb
|
30
|
+
environment:
|
31
|
+
- ROLES_DIRECTORY_CONFIG_FILE=config.ci.dynamo_db.yml
|
32
|
+
- IDENTITY_DIRECTORY_CONFIG_FILE=config.ci.ldap.yml
|
33
|
+
- TEST_ORCHESTRATION_PROVIDER=Staff
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
|