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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +134 -0
- data/lib/lita-gsuite.rb +15 -0
- data/lib/lita/commands/deletion_candidates.rb +49 -0
- data/lib/lita/commands/empty_groups.rb +36 -0
- data/lib/lita/commands/list_activities.rb +27 -0
- data/lib/lita/commands/list_admins.rb +39 -0
- data/lib/lita/commands/no_org_unit.rb +38 -0
- data/lib/lita/commands/suspension_candidates.rb +49 -0
- data/lib/lita/commands/two_factor_off.rb +49 -0
- data/lib/lita/commands/two_factor_stats.rb +56 -0
- data/lib/lita/google_activity.rb +50 -0
- data/lib/lita/google_group.rb +29 -0
- data/lib/lita/google_organisation_unit.rb +16 -0
- data/lib/lita/google_user.rb +69 -0
- data/lib/lita/gsuite.rb +373 -0
- data/lib/lita/gsuite_gateway.rb +101 -0
- data/lib/lita/redis_token_store.rb +48 -0
- data/lib/lita/weekly_schedule.rb +54 -0
- data/lib/lita/window_schedule.rb +50 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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>
|
data/lib/lita-gsuite.rb
ADDED
@@ -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
|