lita-gsuite 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|