ogam 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.rubocop_metrics_todo.yml +59 -0
- data/.rubocop_todo.yml +1 -1
- data/.travis.yml +5 -7
- data/Gemfile.lock +24 -5
- data/README.md +9 -15
- data/bin/ogam +111 -1
- data/lib/ogam/execute.rb +50 -0
- data/lib/ogam/group.rb +27 -0
- data/lib/ogam/group_assigner.rb +132 -0
- data/lib/ogam/job_title.rb +49 -0
- data/lib/ogam/organisation.rb +35 -0
- data/lib/ogam/signature.rb +22 -0
- data/lib/ogam/user.rb +21 -0
- data/lib/ogam/version.rb +1 -1
- data/lib/ogam.rb +9 -0
- data/ogam.gemspec +3 -1
- metadata +41 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4a72e5717ef8b802f79499f53fb603ddc9ba61dbd06fa166c9a3045d877b748
|
|
4
|
+
data.tar.gz: d4cf4b93af132811e4a6ba058e0440ddc60115a414a7c967bb96c0a34363ffbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4443a1c829534b5ebea13518d00e8d7d1b66dd8cfb2a2b095bbec52478392ac9f09cb8fc0d240482dfaa202e04ef210149e310ecc3a65316be51960a72440bf
|
|
7
|
+
data.tar.gz: 93d3527909a5d33d90f66cb70dcd170622ae9a947294548552431052005e3e2cd66235d6930cbe6e4e5f57a02952f09baa36cb9c3804bdad5672dcb42d0d986c
|
data/.rubocop.yml
CHANGED
data/.rubocop_metrics_todo.yml
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metrics/AbcSize:
|
|
2
|
+
Exclude:
|
|
3
|
+
- db/**/*
|
|
4
|
+
- spec/**/*
|
|
5
|
+
- config/routes.rb
|
|
6
|
+
- lib/ogam/group_assigner.rb
|
|
7
|
+
|
|
8
|
+
Metrics/BlockLength:
|
|
9
|
+
Exclude:
|
|
10
|
+
- db/**/*
|
|
11
|
+
- spec/**/*
|
|
12
|
+
- config/routes.rb
|
|
13
|
+
|
|
14
|
+
Metrics/BlockNesting:
|
|
15
|
+
Exclude:
|
|
16
|
+
- db/**/*
|
|
17
|
+
- spec/**/*
|
|
18
|
+
- config/routes.rb
|
|
19
|
+
|
|
20
|
+
Metrics/ClassLength:
|
|
21
|
+
Exclude:
|
|
22
|
+
- db/**/*
|
|
23
|
+
- spec/**/*
|
|
24
|
+
- config/routes.rb
|
|
25
|
+
- lib/ogam/group_assigner.rb
|
|
26
|
+
|
|
27
|
+
Metrics/CyclomaticComplexity:
|
|
28
|
+
Exclude:
|
|
29
|
+
- db/**/*
|
|
30
|
+
- spec/**/*
|
|
31
|
+
- config/routes.rb
|
|
32
|
+
- lib/ogam/group_assigner.rb
|
|
33
|
+
|
|
34
|
+
Metrics/MethodLength:
|
|
35
|
+
Exclude:
|
|
36
|
+
- db/**/*
|
|
37
|
+
- spec/**/*
|
|
38
|
+
- config/routes.rb
|
|
39
|
+
- lib/ogam/group_assigner.rb
|
|
40
|
+
|
|
41
|
+
Metrics/ModuleLength:
|
|
42
|
+
Exclude:
|
|
43
|
+
- db/**/*
|
|
44
|
+
- spec/**/*
|
|
45
|
+
- config/routes.rb
|
|
46
|
+
|
|
47
|
+
Metrics/ParameterLists:
|
|
48
|
+
Exclude:
|
|
49
|
+
- db/**/*
|
|
50
|
+
- spec/**/*
|
|
51
|
+
- config/routes.rb
|
|
52
|
+
|
|
53
|
+
Metrics/PerceivedComplexity:
|
|
54
|
+
Exclude:
|
|
55
|
+
- db/**/*
|
|
56
|
+
- spec/**/*
|
|
57
|
+
- config/routes.rb
|
|
58
|
+
- lib/ogam/group_assigner.rb
|
|
59
|
+
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config --exclude-limit 1000 --no-offense-counts --no-auto-gen-timestamp`
|
|
3
|
-
# using RuboCop version 0.
|
|
3
|
+
# using RuboCop version 0.80.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
data/.travis.yml
CHANGED
|
@@ -3,22 +3,20 @@ language: ruby
|
|
|
3
3
|
cache: bundler
|
|
4
4
|
rvm:
|
|
5
5
|
- 2.6.5
|
|
6
|
-
before_install:
|
|
7
|
-
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
|
|
8
|
-
- gem install bundler -v '~> 1.17.3'
|
|
9
6
|
before_script:
|
|
10
|
-
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64
|
|
7
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64
|
|
8
|
+
> ./cc-test-reporter
|
|
11
9
|
- chmod +x ./cc-test-reporter
|
|
12
|
-
- ./cc-test-reporter before-build
|
|
10
|
+
- './cc-test-reporter before-build'
|
|
13
11
|
script:
|
|
14
12
|
- bundle exec rubocop
|
|
15
13
|
- bundle exec rake
|
|
16
14
|
after_script:
|
|
17
|
-
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
|
15
|
+
- './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
|
|
18
16
|
deploy:
|
|
19
17
|
provider: rubygems
|
|
20
18
|
api_key:
|
|
21
|
-
secure:
|
|
19
|
+
secure: K5oq3QVDaNCEoeDlj9PQgE5oslHTTzzO33SA4XVRPqV/k6S2ASs0iCyAp0LPPFup9NPivw8C64mb41erSY4f/lW8a2tkfVYL28avBiy4n/kioo/hFKjL3WBoUGknsamoDQUBTCrUPtoQE5puducV44cH9AyPFJulXbw4hA8KXisi7xYc+e5NGSsTDhjlj4GBN0G3AOBgZ/x1dqLdG8/NV7Bqwr3fKHqpBEI4nKtGTzWUsoC+4SQVkWKwPzKcme47AiRZa5moKs6xzJQzamJm6sGzk0SQ24PSvDxv/AE6AJe67xbdSzxgN2haSLLTV99AW4ku9dP6JE8i799f9337lmCWNE24GCqlAAURR3GDGtMcv0pUCR12V39wJSdA0C3h4xDriGgSHM69xnY6I4hh0DZCSXMDDAHFQMSF5JU4zOQDKZhme2pUTQwTw0uNoutDp6/YF9kMeq+dzZygKMjtDFVy95IV3N9vXBMHvNQAohNYGVZ47Yy4n6Ss/AOHu6aqWPyFo0VSD4WAzFNJBtZvnMlleS+nRrpxjxaH+LkeqE0VkQU6IFeqipAihEeJDizWMXKcXd7RbFPxmsvcyDHh7kF5th5xCGxqW6O3rC2B64mDNA9NKaROJDMQYszeRb7pOuhoysMnzDOy0CwI9R7jZhlWVSHEZeGv4phMhTKhFGc=
|
|
22
20
|
gem: ogam
|
|
23
21
|
on:
|
|
24
22
|
rvm: 2.6.5
|
data/Gemfile.lock
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
ogam (
|
|
4
|
+
ogam (1.0.0)
|
|
5
|
+
activesupport (~> 6.0.2)
|
|
6
|
+
attr_extras (~> 6.2.3)
|
|
5
7
|
thor (~> 1.0.1)
|
|
6
8
|
|
|
7
9
|
GEM
|
|
8
10
|
remote: https://rubygems.org/
|
|
9
11
|
specs:
|
|
12
|
+
activesupport (6.0.2.1)
|
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
14
|
+
i18n (>= 0.7, < 2)
|
|
15
|
+
minitest (~> 5.1)
|
|
16
|
+
tzinfo (~> 1.1)
|
|
17
|
+
zeitwerk (~> 2.2)
|
|
10
18
|
ast (2.4.0)
|
|
19
|
+
attr_extras (6.2.3)
|
|
20
|
+
concurrent-ruby (1.1.6)
|
|
11
21
|
diff-lcs (1.3)
|
|
12
22
|
docile (1.3.2)
|
|
23
|
+
i18n (1.8.2)
|
|
24
|
+
concurrent-ruby (~> 1.0)
|
|
13
25
|
jaro_winkler (1.5.4)
|
|
14
26
|
json (2.3.0)
|
|
27
|
+
minitest (5.14.0)
|
|
15
28
|
parallel (1.19.1)
|
|
16
29
|
parser (2.7.0.2)
|
|
17
30
|
ast (~> 2.4.0)
|
|
18
31
|
rack (2.2.2)
|
|
19
32
|
rainbow (3.0.0)
|
|
20
33
|
rake (13.0.1)
|
|
34
|
+
rexml (3.2.4)
|
|
21
35
|
rspec (3.9.0)
|
|
22
36
|
rspec-core (~> 3.9.0)
|
|
23
37
|
rspec-expectations (~> 3.9.0)
|
|
@@ -31,15 +45,16 @@ GEM
|
|
|
31
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
32
46
|
rspec-support (~> 3.9.0)
|
|
33
47
|
rspec-support (3.9.2)
|
|
34
|
-
rubocop (0.
|
|
48
|
+
rubocop (0.80.0)
|
|
35
49
|
jaro_winkler (~> 1.5.1)
|
|
36
50
|
parallel (~> 1.10)
|
|
37
51
|
parser (>= 2.7.0.1)
|
|
38
52
|
rainbow (>= 2.2.2, < 4.0)
|
|
53
|
+
rexml
|
|
39
54
|
ruby-progressbar (~> 1.7)
|
|
40
55
|
unicode-display_width (>= 1.4.0, < 1.7)
|
|
41
|
-
rubocop-ogat (1.
|
|
42
|
-
rubocop (= 0.
|
|
56
|
+
rubocop-ogat (1.47.0)
|
|
57
|
+
rubocop (= 0.80.0)
|
|
43
58
|
rubocop-performance (= 1.5.2)
|
|
44
59
|
rubocop-rails (= 2.4.2)
|
|
45
60
|
rubocop-rspec (= 1.38.1)
|
|
@@ -57,7 +72,11 @@ GEM
|
|
|
57
72
|
simplecov-html (~> 0.10.0)
|
|
58
73
|
simplecov-html (0.10.2)
|
|
59
74
|
thor (1.0.1)
|
|
75
|
+
thread_safe (0.3.6)
|
|
76
|
+
tzinfo (1.2.6)
|
|
77
|
+
thread_safe (~> 0.1)
|
|
60
78
|
unicode-display_width (1.6.1)
|
|
79
|
+
zeitwerk (2.2.2)
|
|
61
80
|
|
|
62
81
|
PLATFORMS
|
|
63
82
|
ruby
|
|
@@ -67,7 +86,7 @@ DEPENDENCIES
|
|
|
67
86
|
ogam!
|
|
68
87
|
rake (~> 13.0)
|
|
69
88
|
rspec (~> 3.0)
|
|
70
|
-
rubocop-ogat (~> 1.
|
|
89
|
+
rubocop-ogat (~> 1.47.0)
|
|
71
90
|
simplecov (~> 0.17.1)
|
|
72
91
|
|
|
73
92
|
BUNDLED WITH
|
data/README.md
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
# Ogam
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
|
3
|
+
Wrapper around GAM for common tasks with OGAT's G Suite instance.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
gem 'ogam'
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
And then execute:
|
|
7
|
+
This tool relies on GAM being installed. First install that using the normal installation procedure, then run:
|
|
16
8
|
|
|
17
|
-
$
|
|
9
|
+
$ gem install ogam
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
If running on Google Cloud Shell, ensure the gems bin directory is in your path by appending the following line to ~/.bashrc:
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
`PATH=".gems/bin:$PATH"`
|
|
22
14
|
|
|
23
15
|
## Usage
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
For a list of available commands, run:
|
|
18
|
+
|
|
19
|
+
$ ogam help
|
|
26
20
|
|
|
27
21
|
## Development
|
|
28
22
|
|
|
@@ -32,4 +26,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
32
26
|
|
|
33
27
|
## Contributing
|
|
34
28
|
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
29
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Outwood/ogam.
|
data/bin/ogam
CHANGED
|
@@ -4,9 +4,119 @@
|
|
|
4
4
|
require 'bundler/setup'
|
|
5
5
|
require 'ogam'
|
|
6
6
|
require 'thor'
|
|
7
|
+
require 'shellwords'
|
|
7
8
|
|
|
9
|
+
# Signature subcommand cli
|
|
10
|
+
class SignatureCLI < Thor
|
|
11
|
+
desc('user EMAIL', 'Sets the signature for user with primary email EMAIL based on their name, job title, and org')
|
|
12
|
+
def user(email)
|
|
13
|
+
set_signatures(Ogam::User.active_by_email(email), pretend: parent_options[:pretend])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc('domain DOMAIN', 'Sets the signature for all users @DOMAIN based on their name, job title, and org')
|
|
17
|
+
def domain(domain)
|
|
18
|
+
set_signatures(Ogam::User.active_for_domain(domain), pretend: parent_options[:pretend])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def set_signatures(users, pretend:)
|
|
24
|
+
users.each do |user|
|
|
25
|
+
Ogam::Execute.single(Ogam::Signature.update_signature_command(user), pretend: pretend)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Command line interface for OGAM
|
|
8
31
|
class CLI < Thor
|
|
9
|
-
|
|
32
|
+
class_option :pretend, type: :boolean, default: false
|
|
33
|
+
|
|
34
|
+
desc(
|
|
35
|
+
'assign_to_standard_groups DOMAIN_OR_EMAIL',
|
|
36
|
+
'Assigns users at DOMAIN (e.g. newbold.outwood.com) or EMAIL address to standard groups based on their job title'
|
|
37
|
+
)
|
|
38
|
+
def assign_to_standard_groups(domain_or_email)
|
|
39
|
+
method = domain_or_email.include?('@') ? :for_user : :for_domain
|
|
40
|
+
Ogam::GroupAssigner.public_send(method, domain_or_email, pretend: options[:pretend])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc(
|
|
44
|
+
'populate_titles_and_orgs PATH_TO_CSV',
|
|
45
|
+
'Populates job titles and organisation names from a CSV with columns primary_email, job_title, organisation'
|
|
46
|
+
)
|
|
47
|
+
def populate_titles_and_orgs(csv_path)
|
|
48
|
+
Ogam::Execute.csv_command(
|
|
49
|
+
csv_path,
|
|
50
|
+
'gam update user ~primary_email organization name ~organisation title ~job_title primary',
|
|
51
|
+
pretend: options[:pretend]
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc('create_shared_mailbox EMAIL NAME', 'Create a shared mailbox')
|
|
56
|
+
def create_shared_mailbox(email, name)
|
|
57
|
+
Ogam::Execute.single(Ogam::Group.create_shared_mailbox_command(email, name), pretend: options[:pretend])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
option :moderated, type: :boolean, default: false
|
|
61
|
+
desc('create_group EMAIL NAME [--moderated]', 'Create a distribution list group, optionally moderated')
|
|
62
|
+
def create_group(email, name)
|
|
63
|
+
Ogam::Execute.single(
|
|
64
|
+
Ogam::Group.create_distribution_list_command(email, name, options[:moderated]), pretend: options[:pretend]
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
option :organisation, type: :string
|
|
69
|
+
desc(
|
|
70
|
+
'set_job_title EMAIL TITLE [--organisation ORG_NAME]',
|
|
71
|
+
'Sets job title for a user, and updates their signature, groups, and organisation name. Optionally override org.'
|
|
72
|
+
)
|
|
73
|
+
def set_job_title(email, title)
|
|
74
|
+
validate_job_title!(title)
|
|
75
|
+
|
|
76
|
+
approved_organisations = Ogam::Organisation.all
|
|
77
|
+
org = infer_organisation_if_necessary(options[:organisation], email, approved_organisations)
|
|
78
|
+
validate_organisation!(org, approved_organisations)
|
|
79
|
+
|
|
80
|
+
set_title_and_org_and_make_changes(email, title, org, pretend: options[:pretend])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
desc('set_signature user/domain EMAIL/DOMAIN', 'Set email signatures for users/domains based on user attributes')
|
|
84
|
+
subcommand 'set_signature', SignatureCLI
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def validate_job_title!(title)
|
|
89
|
+
approved_job_titles = Ogam::JobTitle.all
|
|
90
|
+
new_job_titles = Ogam::JobTitle.job_titles(title)
|
|
91
|
+
new_job_titles.each do |s|
|
|
92
|
+
raise ArgumentError, "'#{s}' is not an approved job title." unless approved_job_titles.include?(s)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_organisation!(org, approved_organisations)
|
|
97
|
+
org.split(' / ').each do |org_part|
|
|
98
|
+
unless approved_organisations.value?(org_part)
|
|
99
|
+
raise ArgumentError, "'#{org_part}' is not an approved organisation name."
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def infer_organisation_if_necessary(org, email, approved_organisations)
|
|
105
|
+
return org if org.present?
|
|
106
|
+
|
|
107
|
+
approved_organisations.fetch(email.split('@').last, '')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def set_title_and_org_and_make_changes(email, title, org, pretend: true)
|
|
111
|
+
Ogam::Execute.single(
|
|
112
|
+
"gam update user #{email} organization name #{Shellwords.escape(org)} title #{Shellwords.escape(title)} primary",
|
|
113
|
+
pretend: pretend
|
|
114
|
+
)
|
|
115
|
+
Ogam::GroupAssigner.for_user(email, pretend: pretend)
|
|
116
|
+
Ogam::User.active_by_email(email).each do |user|
|
|
117
|
+
Ogam::Execute.single(Ogam::Signature.update_signature_command(user), pretend: pretend)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
10
120
|
end
|
|
11
121
|
|
|
12
122
|
CLI.start(ARGV)
|
data/lib/ogam/execute.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'csv'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
require 'English'
|
|
6
|
+
|
|
7
|
+
module Ogam
|
|
8
|
+
# Methods to execute GAM commands
|
|
9
|
+
class Execute
|
|
10
|
+
def self.single(command, pretend: true)
|
|
11
|
+
puts "#{pretend ? '[PRETEND] ' : nil}Executing #{command}"
|
|
12
|
+
return if pretend
|
|
13
|
+
|
|
14
|
+
system("~/bin/gam/#{command}", exception: true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.fetch(command, pretend: true)
|
|
18
|
+
puts "#{pretend ? '[PRETEND] ' : nil}Fetching results from #{command}"
|
|
19
|
+
return if pretend
|
|
20
|
+
|
|
21
|
+
`~/bin/gam/#{command}`
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.fetch_csv(command, pretend: true)
|
|
25
|
+
result = fetch(command, pretend: pretend)
|
|
26
|
+
return unless result
|
|
27
|
+
|
|
28
|
+
CSV.parse(result, headers: true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.csv_command(csv_path, command, pretend: true)
|
|
32
|
+
single("gam csv #{Shellwords.escape(csv_path)} #{command}", pretend: pretend)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.multiple(commands, pretend: true)
|
|
36
|
+
return if commands.empty?
|
|
37
|
+
|
|
38
|
+
puts "#{pretend ? '[PRETEND] ' : nil}Executing multiple commands:"
|
|
39
|
+
commands.each do |command|
|
|
40
|
+
puts "\t#{command}"
|
|
41
|
+
end
|
|
42
|
+
return if pretend
|
|
43
|
+
|
|
44
|
+
Tempfile.create do |f|
|
|
45
|
+
commands.each { |command| f.puts command }
|
|
46
|
+
single("gam batch #{f.path}", pretend: pretend)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/ogam/group.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
module Ogam
|
|
5
|
+
# Methods relating to groups
|
|
6
|
+
class Group
|
|
7
|
+
def self.groups_for_domain(domain)
|
|
8
|
+
Ogam::Execute.fetch_csv("gam print groups domain #{domain}", pretend: false).map { |r| r['Email'] }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.add_group_member_command(user:, group:)
|
|
12
|
+
"gam update group #{group} add member user #{user}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.create_shared_mailbox_command(email_address, name)
|
|
16
|
+
<<~COMMAND
|
|
17
|
+
gam create group #{email_address} name #{Shellwords.escape(name)} allow_external_members false who_can_join invited_can_join primary_language en-GB who_can_view_membership all_in_domain_can_view include_in_global_address_list true is_archived true members_can_post_as_the_group false allow_web_posting true send_message_deny_notification false reply_to reply_to_ignore message_moderation_level moderate_none who_can_contact_owner all_in_domain_can_contact who_can_leave_group all_members_can_leave who_can_add all_managers_can_add who_can_post_message anyone_can_post who_can_invite all_managers_can_invite who_can_view_group all_members_can_view show_in_group_directory true archive_only false spam_moderation_level moderate
|
|
18
|
+
COMMAND
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.create_distribution_list_command(email_address, name, moderated)
|
|
22
|
+
<<~COMMAND
|
|
23
|
+
gam create group #{email_address} name #{Shellwords.escape(name)} allow_external_members false who_can_join invited_can_join primary_language en-GB who_can_view_membership all_in_domain_can_view include_in_global_address_list true is_archived false members_can_post_as_the_group false allow_web_posting false send_message_deny_notification true reply_to reply_to_sender message_moderation_level #{moderated ? 'moderate_all_messages' : 'moderate_none'} who_can_contact_owner all_managers_can_contact who_can_leave_group none_can_leave who_can_add none_can_add who_can_post_message all_in_domain_can_post who_can_invite none_can_invite who_can_view_group all_managers_can_view show_in_group_directory true archive_only false spam_moderation_level moderate
|
|
24
|
+
COMMAND
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ogam
|
|
4
|
+
# Assign users from a domain to standard groups based on their job titles
|
|
5
|
+
class GroupAssigner
|
|
6
|
+
static_facade :call, :users, :domain, [pretend: true]
|
|
7
|
+
|
|
8
|
+
SUBJECT_TEACHER_TITLES = ['Teacher of ', 'Head of ', 'Second in ', 'Third in '].freeze
|
|
9
|
+
SUBJECT_MAPPING = {
|
|
10
|
+
'art-teachers' => ['Art'],
|
|
11
|
+
'business-teachers' => ['Business Studies'],
|
|
12
|
+
'dance-teachers' => ['Performing Arts'],
|
|
13
|
+
'design-technology-teachers' => ['Technology'],
|
|
14
|
+
'drama-teachers' => ['Drama', 'Music & Drama', 'Performing Arts'],
|
|
15
|
+
'english-teachers' => ['English'],
|
|
16
|
+
'epq-teachers' => [],
|
|
17
|
+
'geography-teachers' => ['Humanities'],
|
|
18
|
+
'guidance-teachers' => [],
|
|
19
|
+
'health-social-care-teachers' => ['Health & Social Care'],
|
|
20
|
+
'history-teachers' => %w[Humanities History],
|
|
21
|
+
'ict-teachers' => ['Computer Science'],
|
|
22
|
+
'law-teachers' => ['Law'],
|
|
23
|
+
'maths-teachers' => ['Maths'],
|
|
24
|
+
'mfl-teachers' => ['MFL'],
|
|
25
|
+
'music-teachers' => ['Music', 'Music & Drama'],
|
|
26
|
+
'pe-teachers' => ['PE'],
|
|
27
|
+
're-life-teachers' => ['Humanities'],
|
|
28
|
+
'science-teachers' => ['Science'],
|
|
29
|
+
'social-sciences-teachers' => ['Social Sciences']
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
def self.for_domain(domain, pretend: true)
|
|
33
|
+
call(User.active_for_domain(domain), domain, pretend: pretend)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.for_user(email, pretend: true)
|
|
37
|
+
call(User.active_by_email(email), email.split('@').last, pretend: pretend)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.groups_for_job_title(job_title, entry, domain)
|
|
41
|
+
groups_domain = "groups.#{domain}"
|
|
42
|
+
|
|
43
|
+
groups = groups_from_entry(entry, groups_domain)
|
|
44
|
+
groups += subject_groups(job_title, groups_domain) if entry['Teaching']
|
|
45
|
+
|
|
46
|
+
groups << "heads-of-departments@#{groups_domain}" if entry['Teaching'] && job_title.include?('Head of ')
|
|
47
|
+
groups << "principal@#{groups_domain}" if entry['SLT'] && ['Principal', 'Acting Principal'].include?(job_title)
|
|
48
|
+
|
|
49
|
+
groups << "learning-managers@#{groups_domain}" if job_title.include?('Learning Manager')
|
|
50
|
+
groups << "academy-council@#{groups_domain}" if job_title.include?('Academy Council')
|
|
51
|
+
groups << 'business-managers@trust-wide.outwood.com' if job_title == 'Business Manager'
|
|
52
|
+
|
|
53
|
+
groups
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.groups_from_entry(entry, groups_domain)
|
|
57
|
+
groups = []
|
|
58
|
+
groups << "teaching-staff@#{groups_domain}" if entry['Teaching']
|
|
59
|
+
groups << "support-staff@#{groups_domain}" if entry['Support']
|
|
60
|
+
if entry['SLT']
|
|
61
|
+
groups << "slt@#{groups_domain}"
|
|
62
|
+
groups << "leadership-team@#{groups_domain}"
|
|
63
|
+
end
|
|
64
|
+
groups << 'finance@trust-wide.outwood.com' if entry['Finance']
|
|
65
|
+
groups << 'data-exams@trust-wide.outwood.com' if entry['Data and Exams']
|
|
66
|
+
groups << 'human-resources@trust-wide.outwood.com' if entry['HR']
|
|
67
|
+
groups << 'ict-support@trust-wide.outwood.com' if entry['ICT Support']
|
|
68
|
+
groups
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.subject_groups(job_title, groups_domain)
|
|
72
|
+
return [] unless SUBJECT_TEACHER_TITLES.any? { |title| job_title.include?(title) }
|
|
73
|
+
|
|
74
|
+
groups = []
|
|
75
|
+
SUBJECT_MAPPING.each do |group_prefix, subject_words|
|
|
76
|
+
has_subject =
|
|
77
|
+
subject_words.any? do |subject|
|
|
78
|
+
SUBJECT_TEACHER_TITLES.any? { |title| job_title.ends_with? "#{title}#{subject}" }
|
|
79
|
+
end
|
|
80
|
+
next unless has_subject
|
|
81
|
+
|
|
82
|
+
groups << "#{group_prefix}@#{groups_domain}"
|
|
83
|
+
end
|
|
84
|
+
groups
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def call
|
|
88
|
+
Execute.multiple(commands(generate_group_assignments), pretend: pretend)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def commands(group_assignments)
|
|
94
|
+
group_assignments.find_all { |_user, group| valid_groups.include?(group) }.map do |user, group|
|
|
95
|
+
Group.add_group_member_command(user: user, group: group)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def valid_groups
|
|
100
|
+
@valid_groups ||= Group.groups_for_domain("groups.#{domain}") + Group.groups_for_domain('trust-wide.outwood.com')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def generate_group_assignments
|
|
104
|
+
group_assignments = []
|
|
105
|
+
users.each do |user|
|
|
106
|
+
group_assignments += group_assignments_for_user(user)
|
|
107
|
+
end
|
|
108
|
+
group_assignments
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def group_assignments_for_user(user)
|
|
112
|
+
group_assignments = []
|
|
113
|
+
email = user['primaryEmail']
|
|
114
|
+
User.job_titles_for_user(user).each do |job_title|
|
|
115
|
+
entry = approved_job_titles.fetch(job_title, nil)
|
|
116
|
+
if entry.nil?
|
|
117
|
+
puts "Warning: Unapproved job title found for user #{email}: #{job_title}"
|
|
118
|
+
next
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
GroupAssigner.groups_for_job_title(job_title, entry, domain).each do |group|
|
|
122
|
+
group_assignments << [email, group]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
group_assignments
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def approved_job_titles
|
|
129
|
+
@approved_job_titles ||= JobTitle.all
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
require 'csv'
|
|
5
|
+
|
|
6
|
+
module Ogam
|
|
7
|
+
# Allows working with a list of approved job titles
|
|
8
|
+
class JobTitle
|
|
9
|
+
static_facade :all
|
|
10
|
+
|
|
11
|
+
CSV_URL = 'https://docs.google.com/spreadsheets/d/12nl5OBGSmOSsWilzWfflmAHO-8EyfCjVXYLeUDqjB60/export?format=csv&id=12nl5OBGSmOSsWilzWfflmAHO-8EyfCjVXYLeUDqjB60&gid=1604665254'
|
|
12
|
+
|
|
13
|
+
def self.job_titles(str)
|
|
14
|
+
return [] if str.blank?
|
|
15
|
+
|
|
16
|
+
str.split(' / ')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def all
|
|
20
|
+
@all ||= parse(download)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def download
|
|
26
|
+
puts 'Fetching list of accepted job titles'
|
|
27
|
+
URI.parse(CSV_URL).read
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse(str)
|
|
31
|
+
data = {}
|
|
32
|
+
CSV.parse(str, headers: true).each do |row|
|
|
33
|
+
next if row['Job Title'].blank?
|
|
34
|
+
|
|
35
|
+
data[row['Job Title']] = entry_for_row(row)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
data
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def entry_for_row(row)
|
|
42
|
+
entry = {}
|
|
43
|
+
(row.headers - ['Job Title']).each do |col|
|
|
44
|
+
entry[col] = row[col] == 'TRUE'
|
|
45
|
+
end
|
|
46
|
+
entry
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
require 'csv'
|
|
5
|
+
|
|
6
|
+
module Ogam
|
|
7
|
+
# Allows working with a list of approved organisations
|
|
8
|
+
class Organisation
|
|
9
|
+
static_facade :all
|
|
10
|
+
|
|
11
|
+
CSV_URL = 'https://docs.google.com/spreadsheets/d/12nl5OBGSmOSsWilzWfflmAHO-8EyfCjVXYLeUDqjB60/export?format=csv&id=12nl5OBGSmOSsWilzWfflmAHO-8EyfCjVXYLeUDqjB60&gid=46181347'
|
|
12
|
+
|
|
13
|
+
def all
|
|
14
|
+
@all ||= parse(download)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def download
|
|
20
|
+
puts 'Fetching list of accepted organisations'
|
|
21
|
+
URI.parse(CSV_URL).read
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse(str)
|
|
25
|
+
data = {}
|
|
26
|
+
CSV.parse(str, headers: true).each do |row|
|
|
27
|
+
next if row['Domain'].blank?
|
|
28
|
+
|
|
29
|
+
data[row['Domain'].strip] = row['Organisation Name'].strip
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
data
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ogam
|
|
4
|
+
# Methods relating to signatures
|
|
5
|
+
class Signature
|
|
6
|
+
def self.update_signature_command(user)
|
|
7
|
+
email = user.fetch('primaryEmail')
|
|
8
|
+
html = signature_html(
|
|
9
|
+
user.fetch('name.fullName', nil),
|
|
10
|
+
user.fetch('organizations.0.title', nil),
|
|
11
|
+
user.fetch('organizations.0.name', nil)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
"gam user #{email} signature \"#{html}\""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.signature_html(name, job_title, organisation)
|
|
18
|
+
parts = [name, job_title, organisation].reject(&:blank?)
|
|
19
|
+
"<p>#{parts.join('<br />')}</p><hr color=#5b3293 />"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/ogam/user.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ogam
|
|
4
|
+
# Methods relating to users
|
|
5
|
+
class User
|
|
6
|
+
def self.active_for_domain(domain)
|
|
7
|
+
Ogam::Execute.fetch_csv("gam print users allfields domain #{domain} query 'isSuspended=False'", pretend: false)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.active_by_email(email)
|
|
11
|
+
Ogam::Execute.fetch_csv(
|
|
12
|
+
"gam print users allfields query 'email=#{email} isSuspended=False'",
|
|
13
|
+
pretend: false
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.job_titles_for_user(user)
|
|
18
|
+
JobTitle.job_titles(user['organizations.0.title'])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/ogam/version.rb
CHANGED
data/lib/ogam.rb
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/all'
|
|
4
|
+
require 'attr_extras'
|
|
3
5
|
require 'ogam/version'
|
|
6
|
+
require 'ogam/execute'
|
|
7
|
+
require 'ogam/group'
|
|
8
|
+
require 'ogam/job_title'
|
|
9
|
+
require 'ogam/user'
|
|
10
|
+
require 'ogam/group_assigner'
|
|
11
|
+
require 'ogam/signature'
|
|
12
|
+
require 'ogam/organisation'
|
|
4
13
|
|
|
5
14
|
module Ogam
|
|
6
15
|
class Error < StandardError; end
|
data/ogam.gemspec
CHANGED
|
@@ -26,11 +26,13 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.executables << 'ogam'
|
|
27
27
|
spec.require_paths = ['lib']
|
|
28
28
|
|
|
29
|
+
spec.add_runtime_dependency 'activesupport', '~> 6.0.2'
|
|
30
|
+
spec.add_runtime_dependency 'attr_extras', '~> 6.2.3'
|
|
29
31
|
spec.add_runtime_dependency 'thor', '~> 1.0.1'
|
|
30
32
|
|
|
31
33
|
spec.add_development_dependency 'bundler', '~> 2.1'
|
|
32
34
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
33
35
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
34
|
-
spec.add_development_dependency 'rubocop-ogat', '~> 1.
|
|
36
|
+
spec.add_development_dependency 'rubocop-ogat', '~> 1.47.0'
|
|
35
37
|
spec.add_development_dependency 'simplecov', '~> 0.17.1'
|
|
36
38
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ogam
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Elliot Bowes
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-02-
|
|
11
|
+
date: 2020-02-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 6.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: 6.0.2
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: attr_extras
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 6.2.3
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 6.2.3
|
|
13
41
|
- !ruby/object:Gem::Dependency
|
|
14
42
|
name: thor
|
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,14 +100,14 @@ dependencies:
|
|
|
72
100
|
requirements:
|
|
73
101
|
- - "~>"
|
|
74
102
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 1.
|
|
103
|
+
version: 1.47.0
|
|
76
104
|
type: :development
|
|
77
105
|
prerelease: false
|
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
107
|
requirements:
|
|
80
108
|
- - "~>"
|
|
81
109
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 1.
|
|
110
|
+
version: 1.47.0
|
|
83
111
|
- !ruby/object:Gem::Dependency
|
|
84
112
|
name: simplecov
|
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -117,6 +145,13 @@ files:
|
|
|
117
145
|
- bin/ogam
|
|
118
146
|
- bin/setup
|
|
119
147
|
- lib/ogam.rb
|
|
148
|
+
- lib/ogam/execute.rb
|
|
149
|
+
- lib/ogam/group.rb
|
|
150
|
+
- lib/ogam/group_assigner.rb
|
|
151
|
+
- lib/ogam/job_title.rb
|
|
152
|
+
- lib/ogam/organisation.rb
|
|
153
|
+
- lib/ogam/signature.rb
|
|
154
|
+
- lib/ogam/user.rb
|
|
120
155
|
- lib/ogam/version.rb
|
|
121
156
|
- ogam.gemspec
|
|
122
157
|
homepage: https://github.com/Outwood/ogam
|
|
@@ -139,7 +174,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
174
|
- !ruby/object:Gem::Version
|
|
140
175
|
version: '0'
|
|
141
176
|
requirements: []
|
|
142
|
-
|
|
177
|
+
rubyforge_project:
|
|
178
|
+
rubygems_version: 2.7.7
|
|
143
179
|
signing_key:
|
|
144
180
|
specification_version: 4
|
|
145
181
|
summary: Wrapper around GAM for common tasks with OGAT's G Suite instance.
|