lita-gsuite 0.5

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: 39fffd8c5976caaef1dd7c8b68a7562f95aa6f9b
4
+ data.tar.gz: 342a2b5efcf4f14b2f66a2083ebb201ee9c521dc
5
+ SHA512:
6
+ metadata.gz: 6df288e9de29014dbae5c9523441cfbf4b9bde4d67850ce04ac627963d564830dd32cc06c71e88346baf88f295d8a6f2360954fbaa3b320a1928ab1be6bec1bf
7
+ data.tar.gz: 145eb15b7d0ed9e454cd5bbdab45ad5a4a25470805bb46455e7dee8c34d5706634e1fe2172735092ee06f688f6f71e08ef3db15976a68d318e7c2824e3cae150
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 James Healy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,134 @@
1
+ # lita-gsuite
2
+
3
+ A lita plugin for interacting with a GSuite account.
4
+
5
+ By design, only read-only access is requested. This is intended to provide some visibility
6
+ into the account, not provide administrative functions.
7
+
8
+ This was written for a GSuite account with ~125 active users. It may not scale
9
+ well to larger accounts, but feedback and optimisations are welcome.
10
+
11
+ ## Installation
12
+
13
+ Add this gem to your lita installation by including the following line in your Gemfile:
14
+
15
+ gem "lita-gsuite", git: "http://github.com/yob/lita-gsuite.git"
16
+
17
+ ## Configuration
18
+
19
+ Edit your lita\_config.rb to include the following lines lines. Some of the
20
+ values are sensitive, so using ENV vars is recommended to keep the values out
21
+ of version control.
22
+
23
+ When an API call to google is required, we want to make it with tokens that
24
+ are tied to the specific user that requested data. To do so, we use Google's
25
+ OAuth2 support.
26
+
27
+ That requires an OAuth2 client ID and secret - see "Authentication" below for more
28
+ details on how to generate these:
29
+
30
+ config.handlers.gsuite.oauth_client_id = ENV["GOOGLE_CLIENT_ID"]
31
+ config.handlers.gsuite.oauth_client_secret = ENV["GOOGLE_CLIENT_SECRET"]
32
+
33
+ ## Authentication
34
+
35
+ The lita bot requires an OAuth client ID and secret before it can initiate
36
+ the process to generate an OAuth2 token for each user.
37
+
38
+ These can be created on the [Google Developers
39
+ Console](https://console.developers.google.com/), and Google has [some
40
+ documentation](https://developers.google.com/identity/protocols/OAuth2).
41
+
42
+ You should be given the opportunity to view the new ID and secret. Be sure to copy them
43
+ down, as they can't be retrieved again later.
44
+
45
+ Once the handler is configured and running, each user that wants to interact with it
46
+ will be prompted to complete an OAuth authorisation process before they can start. This
47
+ generates an API token that's specific to them and will be used to make API calls on
48
+ their behalf.
49
+
50
+ ## Enable Google API
51
+
52
+ The GSuite API must be explicitly enabled for your account, and the new service account
53
+ must be whitelisted before it can access any data.
54
+
55
+ 1. Sign in to https://admin.google.com
56
+ 2. Visit the Security tab, click "API reference" and "Enable API Access"
57
+
58
+ ## Chat commands
59
+
60
+ ### Administrators
61
+
62
+ List users with super or delegated administrative privileges, and their two-factor
63
+ auth status.
64
+
65
+ lita gsuite list-admins
66
+
67
+ ### Empty Groups
68
+
69
+ List groups with no members.
70
+
71
+ lita gsuite empty-groups
72
+
73
+ ### Inactive Users
74
+
75
+ List active users that haven't logged in for 8 weeks. This may be helpful for
76
+ identifying accounts that should be suspended or deleted.
77
+
78
+ lita gsuite suspension-candidates
79
+
80
+ ### Suspended Users
81
+
82
+ List suspended users that haven't logged in for 26 weeks. This may be helpful
83
+ for identifying accounts that have been suspended for a long time and may be
84
+ candidates for deletion.
85
+
86
+ lita gsuite deletion-candidates
87
+
88
+ ### User with No Organisational Unit
89
+
90
+ List users not assigned to an Organisational Unit.
91
+
92
+ lita gsuite no-ou
93
+
94
+ ### Two Factor Authentication
95
+
96
+ Print key stats on Second Factor Authentication uptake.
97
+
98
+ lita gsuite two-factor-stats
99
+
100
+ ### Two Factor Authentication Disabled
101
+
102
+ Print users within an Organisational Unit that don't have Second Factor Authentication enabled.
103
+
104
+ lita gsuite two-factor-off /OUPATH
105
+
106
+ ## Periodic Updates
107
+
108
+ To help monitor the above reports automatically, it's possible to schedule them to be printed in
109
+ a channel periodically.
110
+
111
+ List the reports that will periodically print in the current channel:
112
+
113
+ lita gsuite schedule list
114
+
115
+ List the reports that are available to print periodically:
116
+
117
+ lita gsuite schedule commands
118
+
119
+ Schedule a weekly report in the current channel, specified in UTC time:
120
+
121
+ lita gsuite schedule add-weekly <day-of-the-week> HH:MM <report-name>
122
+ lita gsuite schedule add-weekly wednesday 01:00 list-admins
123
+ lita gsuite schedule add-weekly monday 13:45 no-ou
124
+
125
+ Schedule a time-window report that will run regularly and print output when
126
+ new data is available:
127
+
128
+ lita gsuite schedule add-window <report-name>
129
+ lita gsuite schedule add-window list-activities
130
+
131
+ Delete a scheduled report from the current room. The ID can be find in the
132
+ output of "schedule list":
133
+
134
+ lita gsuite schedule del <id>
@@ -0,0 +1,15 @@
1
+ require "lita/commands/deletion_candidates"
2
+ require "lita/commands/empty_groups"
3
+ require "lita/commands/list_admins"
4
+ require "lita/commands/list_activities"
5
+ require "lita/commands/no_org_unit"
6
+ require "lita/commands/suspension_candidates"
7
+ require "lita/commands/two_factor_off"
8
+ require "lita/commands/two_factor_stats"
9
+
10
+ require "lita/weekly_schedule"
11
+ require "lita/window_schedule"
12
+
13
+ require "lita/gsuite_gateway"
14
+ require "lita/gsuite"
15
+ require "lita/redis_token_store"
@@ -0,0 +1,49 @@
1
+ module Lita
2
+ module Commands
3
+
4
+ class DeletionCandidates
5
+ MAX_WEEKS_SUSPENDED = 26
6
+
7
+ def name
8
+ 'deletion-candidates'
9
+ end
10
+
11
+ def run(robot, target, gateway, opts = {})
12
+ msg = build_msg(gateway)
13
+ robot.send_message(target, msg) if msg
14
+ robot.send_message(target, "No users found") if msg.nil? && opts[:negative_ack]
15
+ end
16
+
17
+ private
18
+
19
+ def build_msg(gateway)
20
+ users = long_term_suspended_users(gateway)
21
+ if users.any?
22
+ msg = "The following users are suspended, and have not logged in for #{MAX_WEEKS_SUSPENDED} weeks. "
23
+ msg += "If appropriate, consider deleting their accounts:\n"
24
+ msg += users.map { |user|
25
+ "- #{user.path}"
26
+ }.sort.join("\n")
27
+ end
28
+ end
29
+
30
+ def long_term_suspended_users(gateway)
31
+ timestamp = max_weeks_suspended_ago
32
+
33
+ gateway.users.select { |user|
34
+ user.suspended?
35
+ }.select { |user|
36
+ user.last_login_at < timestamp
37
+ }
38
+ end
39
+
40
+ def max_weeks_suspended_ago
41
+ (Time.now.utc - weeks_in_seconds(MAX_WEEKS_SUSPENDED)).to_datetime
42
+ end
43
+
44
+ def weeks_in_seconds(weeks)
45
+ 60 * 60 * 24 * 7 * weeks.to_i
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module Lita
2
+ module Commands
3
+
4
+ class EmptyGroups
5
+
6
+ def name
7
+ 'empty-groups'
8
+ end
9
+
10
+ def run(robot, target, gateway, opts = {})
11
+ msg = build_msg(gateway)
12
+ robot.send_message(target, msg) if msg
13
+ robot.send_message(target, "No groups found") if msg.nil? && opts[:negative_ack]
14
+ end
15
+
16
+ private
17
+
18
+ def build_msg(gateway)
19
+ groups = empty_groups(gateway)
20
+
21
+ if groups.any?
22
+ msg = "The following groups have no members, which may result in undelivered email.\n"
23
+ msg += groups.map { |group|
24
+ "- #{group.email}"
25
+ }.join("\n")
26
+ end
27
+ end
28
+
29
+ def empty_groups(gateway)
30
+ gateway.groups.select { |group|
31
+ group.member_count == 0
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ module Lita
2
+ module Commands
3
+
4
+ class ListActivities
5
+
6
+ def name
7
+ 'list-activities'
8
+ end
9
+
10
+ def duration_minutes
11
+ 10
12
+ end
13
+
14
+ def buffer_minutes
15
+ 10
16
+ end
17
+
18
+ def run(robot, target, gateway, window_start, window_end)
19
+ activities = gateway.admin_activities(window_start, window_end)
20
+ activities.sort_by(&:time).map(&:to_msg).each_with_index do |message, index|
21
+ robot.send_message(target, message)
22
+ sleep(1) # TODO ergh. required to stop slack disconnecting us for high sending rates
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module Lita
2
+ module Commands
3
+
4
+ class ListAdmins
5
+
6
+ def name
7
+ 'list-admins'
8
+ end
9
+
10
+ def run(robot, target, gateway, opts = {})
11
+ msg = build_msg(gateway)
12
+ robot.send_message(target, msg) if msg
13
+ robot.send_message(target, "No admins found") if msg.nil? && opts[:negative_ack]
14
+ end
15
+
16
+ private
17
+
18
+ def build_msg(gateway)
19
+ users = all_admins(gateway)
20
+
21
+ if users.any?
22
+ msg = "The following accounts have administrative privileges:\n"
23
+ msg += users.map { |user|
24
+ "- #{user.ou_path}/#{user.email} (2fa enabled: #{tfa?(user)})"
25
+ }.join("\n")
26
+ end
27
+ end
28
+
29
+ def all_admins(gateway)
30
+ (gateway.delegated_admins + gateway.super_admins).uniq.sort_by(&:path)
31
+ end
32
+
33
+ def tfa?(user)
34
+ user.two_factor_enabled? ? "Y" : "N"
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ module Lita
2
+ module Commands
3
+ class NoOrgUnit
4
+
5
+ def name
6
+ 'no-org-unit'
7
+ end
8
+
9
+ def run(robot, target, gateway, opts = {})
10
+ msg = build_msg(gateway)
11
+ robot.send_message(target, msg) if msg
12
+ robot.send_message(target, "No users are missing an org unit") if msg.nil? && opts[:negative_ack]
13
+ end
14
+
15
+ private
16
+
17
+ def build_msg(gateway)
18
+ users = no_org_unit_users(gateway)
19
+
20
+ if users.any?
21
+ msg = "The following users are not assigned to an organisational unit:\n"
22
+ msg += users.sort_by(&:path).map { |user|
23
+ "- #{user.email}"
24
+ }.join("\n")
25
+ end
26
+ end
27
+
28
+ def no_org_unit_users(gateway)
29
+ gateway.users.reject { |user|
30
+ user.suspended?
31
+ }.select { |user|
32
+ user.ou_path == "/"
33
+ }
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ module Lita
2
+ module Commands
3
+ class SuspensionCandidates
4
+ MAX_WEEKS_WITHOUT_LOGIN = 8
5
+
6
+ def name
7
+ 'suspension-candidates'
8
+ end
9
+
10
+ def run(robot, target, gateway, opts = {})
11
+ msg = build_msg(gateway)
12
+ robot.send_message(target, msg) if msg
13
+ robot.send_message(target, "No users found") if msg.nil? && opts[:negative_ack]
14
+ end
15
+
16
+ private
17
+
18
+ def build_msg(gateway)
19
+ users = active_users_with_no_recent_login(gateway)
20
+
21
+ if users.any?
22
+ msg = "The following users have active accounts, but have not logged in for #{MAX_WEEKS_WITHOUT_LOGIN} weeks. "
23
+ msg += "If appropriate, consider suspending or deleting their accounts:\n"
24
+ msg += users.map { |user|
25
+ "- #{user.path}"
26
+ }.sort.join("\n")
27
+ end
28
+ end
29
+
30
+ def active_users_with_no_recent_login(gateway)
31
+ timestamp = max_weeks_without_login_ago
32
+
33
+ gateway.users.reject { |user|
34
+ user.suspended?
35
+ }.select { |user|
36
+ user.last_login_at < timestamp && user.created_at < timestamp
37
+ }
38
+ end
39
+
40
+ def max_weeks_without_login_ago
41
+ (Time.now.utc - weeks_in_seconds(MAX_WEEKS_WITHOUT_LOGIN)).to_datetime
42
+ end
43
+
44
+ def weeks_in_seconds(weeks)
45
+ 60 * 60 * 24 * 7 * weeks.to_i
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module Lita
2
+ module Commands
3
+
4
+ class TwoFactorOff
5
+
6
+ def initialize(ou_path = "/")
7
+ @ou_path = ou_path
8
+ end
9
+
10
+ def name
11
+ 'two-factor-off'
12
+ end
13
+
14
+ def run(robot, target, gateway, opts = {})
15
+ msg = build_msg(gateway)
16
+ robot.send_message(target, msg) if msg
17
+ if msg.nil? && opts[:negative_ack]
18
+ robot.send_message(target, "All users in #{@ou_path} have Two Factor Authentication enabled")
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def build_msg(gateway)
25
+ users = active_users_without_tfa(gateway)
26
+
27
+ if users.any?
28
+ msg = "Users in #{@ou_path} with Two Factor Authentication disabled:\n\n"
29
+ users.each do |user|
30
+ msg += "- #{user.email}\n"
31
+ end
32
+ msg
33
+ end
34
+ end
35
+
36
+ def active_users_without_tfa(gateway)
37
+ gateway.users.reject { |user|
38
+ user.suspended?
39
+ }.reject { |user|
40
+ user.two_factor_enabled?
41
+ }.select { |user|
42
+ user.ou_path == @ou_path
43
+ }.sort_by { |user|
44
+ user.path
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end