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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bd4c39f063cc77d960339f8a828b160734f410e6b31d3ecb0cec94e5ddfaf21
4
- data.tar.gz: 83a9fd885e54efff8ba93672d42bb32b517f98f1478893c43939ad7cfaa1f31f
3
+ metadata.gz: d4a72e5717ef8b802f79499f53fb603ddc9ba61dbd06fa166c9a3045d877b748
4
+ data.tar.gz: d4cf4b93af132811e4a6ba058e0440ddc60115a414a7c967bb96c0a34363ffbd
5
5
  SHA512:
6
- metadata.gz: 309fbc943d2d17660050afa90b18c3a2c08329f85fdd2dfaefd92d7c93c8a37ccae3a62e3cf8b5a957299f840269d69c40813811e40c8c37d87f5aea71dbf6c2
7
- data.tar.gz: 7d38bbbedda7261c9dc127626fdeef0cd1b2bd41f59812fb7a359fae72e5682ccb22165ffed2ae354bbe1481d90dff4e9718b73656ee5088f24d742b63e44cc7
6
+ metadata.gz: d4443a1c829534b5ebea13518d00e8d7d1b66dd8cfb2a2b095bbec52478392ac9f09cb8fc0d240482dfaa202e04ef210149e310ecc3a65316be51960a72440bf
7
+ data.tar.gz: 93d3527909a5d33d90f66cb70dcd170622ae9a947294548552431052005e3e2cd66235d6930cbe6e4e5f57a02952f09baa36cb9c3804bdad5672dcb42d0d986c
data/.rubocop.yml CHANGED
@@ -16,3 +16,9 @@ Metrics/BlockLength:
16
16
  Exclude:
17
17
  - 'spec/**/*.rb'
18
18
  - 'ogatstyle.gemspec'
19
+
20
+ Rails/Output:
21
+ Enabled: false
22
+
23
+ Rails/SaveBang:
24
+ Enabled: false
@@ -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.79.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 > ./cc-test-reporter
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: kazURR5idcANlaYnqiGLnkXfsYWluTc3H87sg73H6s2JwEkA69FieZA3tUrDVX8kfbOxsqDphyv3fZmqrQtmG74ksUPVGsouoth7gpUMNh6Uj8Hr/e3Cgt/4b/SeGCAdpIsx869w8fA9kEKuf1NhQ0taazfiqToau3amyal6QblxMaalX8ThvWtO98ViJkEeIszxRK+uzd3t1aIEW/Lds9jWUm9Rf1tI/ajlRecwStA74pCQHJ2wuPQRcAB8DYz82pNjlyGwIZd14Gmbys0en+r+av3tYLjzwN+z9y7vlXwp1xAeRKU9GoJCHuevKmT1joRnFFfp3xGoKn63b6yS7KHVFT7v4l+qRbMnL+m/wrz5Xv3ZUyk0un9O3+MFLy0jIYP1adcvh+KkqenAktzSekjGstZn8oRk8lAEYfGLUD45nijdGjbYF7VAUPhoP5g6vxGlbZDCHslHuLGMjiEd/vP+SGEhtEMzk6cvir1yoMzryilRTfBfkut7iVsy/MaMvTShxF0qd1hdtx+Htfcq2zad98lAASsciz8su6u9vaqmqyxLKxdMptbWks4bCd0lB9g6fjIT79cIzjwQB6Hv3WNDpMLGDxWH8PkZf4ZEIlvV1goLlzt5t0SoZryn1hWbO4VJMvGLXtVj9MvdgskN95Ww9FTvO3wlm0PVK5iQWkU=
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 (0.1.0)
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.79.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.46.1)
42
- rubocop (= 0.79.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.46.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
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ogam`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- Add this line to your application's Gemfile:
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
- $ bundle
9
+ $ gem install ogam
18
10
 
19
- Or install it yourself as:
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
- $ gem install ogam
13
+ `PATH=".gems/bin:$PATH"`
22
14
 
23
15
  ## Usage
24
16
 
25
- TODO: Write usage instructions here
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/elliotb/ogam.
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
- # contents of the Thor class
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)
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ogam
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.0'
5
5
  end
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.46.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: 0.1.0
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-18 00:00:00.000000000 Z
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.46.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.46.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
- rubygems_version: 3.1.2
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.