openstax_accounts 4.1.1 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -1
- data/app/controllers/openstax/accounts/application_controller.rb +1 -1
- data/app/models/openstax/accounts/account.rb +11 -6
- data/app/models/openstax/accounts/anonymous_account.rb +1 -1
- data/app/models/openstax/accounts/application_account.rb +2 -1
- data/app/models/openstax/accounts/group.rb +14 -12
- data/app/models/openstax/accounts/group_member.rb +10 -10
- data/app/models/openstax/accounts/group_nesting.rb +7 -13
- data/app/models/openstax/accounts/group_owner.rb +10 -10
- data/app/representers/openstax/accounts/api/v1/account_representer.rb +4 -3
- data/app/representers/openstax/accounts/api/v1/application_account_representer.rb +7 -2
- data/app/representers/openstax/accounts/api/v1/application_accounts_representer.rb +0 -2
- data/app/representers/openstax/accounts/api/v1/application_group_representer.rb +7 -2
- data/app/representers/openstax/accounts/api/v1/application_groups_representer.rb +0 -2
- data/app/representers/openstax/accounts/api/v1/group_nesting_representer.rb +1 -1
- data/app/representers/openstax/accounts/api/v1/group_representer.rb +1 -1
- data/app/representers/openstax/accounts/api/v1/group_user_representer.rb +1 -1
- data/app/routines/openstax/accounts/search_accounts.rb +1 -1
- data/app/routines/openstax/accounts/sync_accounts.rb +24 -27
- data/app/routines/openstax/accounts/sync_groups.rb +22 -26
- data/lib/openstax/accounts/action_controller/base.rb +61 -0
- data/lib/openstax/accounts/api.rb +270 -0
- data/lib/openstax/accounts/configuration.rb +77 -0
- data/lib/openstax/accounts/engine.rb +13 -11
- data/lib/openstax/accounts/has_many_through_groups/active_record/base.rb +51 -0
- data/lib/openstax/accounts/version.rb +1 -1
- data/lib/openstax_accounts.rb +8 -331
- data/spec/dummy/app/controllers/api/users_controller.rb +4 -0
- data/spec/dummy/config/routes.rb +3 -1
- data/spec/dummy/log/test.log +152307 -0
- data/spec/lib/openstax/accounts/api_spec.rb +232 -0
- data/spec/lib/openstax/accounts/has_many_through_groups/active_record/base_spec.rb +57 -0
- data/spec/routines/openstax/accounts/sync_accounts_spec.rb +5 -9
- data/spec/routines/openstax/accounts/sync_groups_spec.rb +38 -39
- metadata +27 -16
- data/lib/openstax/accounts/extend_builtins.rb +0 -50
- data/lib/openstax/accounts/has_many_through_groups.rb +0 -47
- data/spec/lib/openstax/accounts/has_many_through_groups_spec.rb +0 -53
- data/spec/lib/openstax_accounts_spec.rb +0 -210
@@ -17,42 +17,38 @@ module OpenStax
|
|
17
17
|
|
18
18
|
def exec(options={})
|
19
19
|
|
20
|
-
|
21
|
-
OpenStax::Accounts.syncing = true
|
20
|
+
return if OpenStax::Accounts.configuration.enable_stubbing?
|
22
21
|
|
23
|
-
|
22
|
+
response = OpenStax::Accounts::Api.get_application_group_updates
|
24
23
|
|
25
|
-
|
24
|
+
app_groups = []
|
25
|
+
app_groups_rep = OpenStax::Accounts::Api::V1::ApplicationGroupsRepresenter
|
26
|
+
.new(app_groups)
|
27
|
+
app_groups_rep.from_json(response.body)
|
26
28
|
|
27
|
-
|
28
|
-
app_groups_rep = OpenStax::Accounts::Api::V1::ApplicationGroupsRepresenter
|
29
|
-
.new(app_groups)
|
30
|
-
app_groups_rep.from_json(response.body)
|
29
|
+
return if app_groups.empty?
|
31
30
|
|
32
|
-
|
31
|
+
updated_app_groups = []
|
32
|
+
app_groups.each do |app_group|
|
33
|
+
group = OpenStax::Accounts::Group.where(
|
34
|
+
:openstax_uid => app_group.group.openstax_uid
|
35
|
+
).first || app_group.group
|
36
|
+
group.syncing = true
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
:openstax_uid => app_group.group.openstax_uid).first || app_group.group
|
38
|
-
|
39
|
-
if group != app_group.group
|
40
|
-
SYNC_ATTRIBUTES.each do |attribute|
|
41
|
-
group.send("#{attribute}=", app_group.group.send(attribute))
|
42
|
-
end
|
38
|
+
if group != app_group.group
|
39
|
+
SYNC_ATTRIBUTES.each do |attribute|
|
40
|
+
group.send("#{attribute}=", app_group.group.send(attribute))
|
43
41
|
end
|
44
|
-
|
45
|
-
next unless group.save
|
46
|
-
|
47
|
-
updated_app_groups << {group_id: group.openstax_uid,
|
48
|
-
read_updates: app_group.unread_updates}
|
49
42
|
end
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
44
|
+
next unless group.save
|
45
|
+
|
46
|
+
updated_app_groups << {group_id: group.openstax_uid,
|
47
|
+
read_updates: app_group.unread_updates}
|
54
48
|
end
|
55
49
|
|
50
|
+
OpenStax::Accounts::Api.mark_group_updates_as_read(updated_app_groups)
|
51
|
+
|
56
52
|
end
|
57
53
|
|
58
54
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Accounts
|
3
|
+
module ActionController
|
4
|
+
module Base
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.helper_method :current_user, :signed_in?
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the current user
|
11
|
+
def current_user
|
12
|
+
current_user_manager.current_user
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the current account
|
16
|
+
def current_account
|
17
|
+
current_user_manager.current_account
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns true iff there is a user signed in
|
21
|
+
def signed_in?
|
22
|
+
current_user_manager.signed_in?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Signs in the given account or user
|
26
|
+
def sign_in(user)
|
27
|
+
current_user_manager.sign_in(user)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Signs out the current account and user
|
31
|
+
def sign_out!
|
32
|
+
current_user_manager.sign_out!
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def current_user_manager
|
38
|
+
@current_user_manager ||= \
|
39
|
+
OpenStax::Accounts::CurrentUserManager.new(request, session, cookies)
|
40
|
+
end
|
41
|
+
|
42
|
+
def authenticate_user!
|
43
|
+
account = current_account
|
44
|
+
|
45
|
+
return if account && !account.is_anonymous?
|
46
|
+
|
47
|
+
store_url key: :accounts_return_to, strategies: [:session]
|
48
|
+
|
49
|
+
respond_to do |format|
|
50
|
+
format.html { redirect_to openstax_accounts.login_url }
|
51
|
+
format.json { head(:forbidden) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
::ActionController::Base.send :include,
|
61
|
+
OpenStax::Accounts::ActionController::Base
|
@@ -0,0 +1,270 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Accounts
|
3
|
+
module Api
|
4
|
+
|
5
|
+
DEFAULT_API_VERSION = :v1
|
6
|
+
|
7
|
+
# Executes an OpenStax Accounts API request,
|
8
|
+
# using the given HTTP method, API path and request options.
|
9
|
+
# Any options accepted by OAuth2 requests can be used, such as
|
10
|
+
# :params, :body, :headers, etc, plus the :access_token option, which can
|
11
|
+
# be used to manually specify an OAuth access token.
|
12
|
+
# On failure, it can throw Faraday::ConnectionFailed for connection errors
|
13
|
+
# or OAuth2::Error if Accounts returns an HTTP 400 error,
|
14
|
+
# such as 422 Unprocessable Entity.
|
15
|
+
# On success, returns an OAuth2::Response object.
|
16
|
+
def self.request(http_method, path, options = {})
|
17
|
+
version = options.delete(:api_version) || DEFAULT_API_VERSION
|
18
|
+
unless version.blank?
|
19
|
+
options[:headers] ||= {}
|
20
|
+
options[:headers].merge!({
|
21
|
+
'Accept' => "application/vnd.accounts.openstax.#{version.to_s}"
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
token_string = options.delete(:access_token)
|
26
|
+
token = token_string.blank? ? client.client_credentials.get_token :
|
27
|
+
OAuth2::AccessToken.new(client, token_string)
|
28
|
+
|
29
|
+
accounts_url = OpenStax::Accounts.configuration.openstax_accounts_url
|
30
|
+
url = URI.join(accounts_url, 'api/', path)
|
31
|
+
|
32
|
+
token.request(http_method, url, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Performs an API request using the given account's access token
|
36
|
+
def self.request_for_account(account, http_method, path, options = {})
|
37
|
+
request(http_method, path,
|
38
|
+
options.merge(access_token: account.access_token))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Performs an account search in the Accounts server.
|
42
|
+
# Results are limited to 10 accounts maximum.
|
43
|
+
# Takes a query parameter and an options hash.
|
44
|
+
# On failure, throws an Exception, just like the request method.
|
45
|
+
# On success, returns an OAuth2::Response object.
|
46
|
+
def self.search_accounts(query, options = {})
|
47
|
+
request(:get, 'users', options.merge(params: {:q => query}))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Updates a user account in the Accounts server.
|
51
|
+
# The account is determined by the OAuth access token.
|
52
|
+
# Also takes an options hash.
|
53
|
+
# On failure, throws an Exception, just like the request method.
|
54
|
+
# On success, returns an OAuth2::Response object.
|
55
|
+
def self.update_account(account, options = {})
|
56
|
+
request_for_account(
|
57
|
+
account, :put, 'user', options.merge(
|
58
|
+
body: account.attributes.slice('username', 'first_name',
|
59
|
+
'last_name', 'full_name',
|
60
|
+
'title').to_json
|
61
|
+
)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Performs an account search in the Accounts server.
|
66
|
+
# Results are limited to accounts that have used the current app.
|
67
|
+
# Takes a query parameter and an options hash.
|
68
|
+
# On failure, throws an Exception, just like the request method.
|
69
|
+
# On success, returns an OAuth2::Response object.
|
70
|
+
def self.search_application_accounts(query, options = {})
|
71
|
+
request(:get, 'application_users', options.merge(params: {:q => query}))
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retrieves information about accounts that have been
|
75
|
+
# recently updated.
|
76
|
+
# Results are limited to accounts that have used the current app.
|
77
|
+
# Takes an options hash.
|
78
|
+
# On failure, throws an Exception, just like the request method.
|
79
|
+
# On success, returns an OAuth2::Response object.
|
80
|
+
def self.get_application_account_updates(options = {})
|
81
|
+
request(:get, 'application_users/updates', options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Marks account updates as "read".
|
85
|
+
# The application_users parameter is an array of hashes.
|
86
|
+
# Each hash has 2 required fields: 'id', which should contain the
|
87
|
+
# application_user's id, and 'read_updates', which should contain
|
88
|
+
# the last received value of unread_updates for that application_user.
|
89
|
+
# Can only be called for application_users that belong to the current app.
|
90
|
+
# Also takes an options hash.
|
91
|
+
# On failure, throws an Exception, just like the request method.
|
92
|
+
# On success, returns an OAuth2::Response object.
|
93
|
+
def self.mark_account_updates_as_read(application_users, options = {})
|
94
|
+
request(:put, 'application_users/updated', options.merge(
|
95
|
+
body: application_users.to_json
|
96
|
+
))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Retrieves information about groups that have been
|
100
|
+
# recently updated.
|
101
|
+
# Results are limited to groups that users of the current app
|
102
|
+
# have access to.
|
103
|
+
# Takes an options hash.
|
104
|
+
# On failure, throws an Exception, just like the request method.
|
105
|
+
# On success, returns an OAuth2::Response object.
|
106
|
+
def self.get_application_group_updates(options = {})
|
107
|
+
request(:get, 'application_groups/updates', options)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Marks group updates as "read".
|
111
|
+
# The application_groups parameter is an array of hashes.
|
112
|
+
# Each hash has 2 required fields: 'id', which should contain the
|
113
|
+
# application_group's id, and 'read_updates', which should contain
|
114
|
+
# the last received value of unread_updates for that application_group.
|
115
|
+
# Can only be called for application_groups that belong to the current app.
|
116
|
+
# Also takes an options hash.
|
117
|
+
# On failure, throws an Exception, just like the request method.
|
118
|
+
# On success, returns an OAuth2::Response object.
|
119
|
+
def self.mark_group_updates_as_read(application_groups, options = {})
|
120
|
+
request(:put, 'application_groups/updated', options.merge(
|
121
|
+
body: application_groups.to_json
|
122
|
+
))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Creates a group in the Accounts server.
|
126
|
+
# The given account will be the owner of the group.
|
127
|
+
# Also takes an options hash.
|
128
|
+
# On failure, throws an Exception, just like the request method.
|
129
|
+
# On success, returns a hash containing the group attributes
|
130
|
+
def self.create_group(account, group, options = {})
|
131
|
+
response = ActiveSupport::JSON.decode(
|
132
|
+
request_for_account(account, :post, 'groups', options.merge(
|
133
|
+
body: group.attributes.slice('name', 'is_public').to_json
|
134
|
+
)).body
|
135
|
+
)
|
136
|
+
group.openstax_uid = response['id']
|
137
|
+
response
|
138
|
+
end
|
139
|
+
|
140
|
+
# Updates a group in the Accounts server.
|
141
|
+
# The given account must own the group.
|
142
|
+
# Also takes an options hash.
|
143
|
+
# On failure, throws an Exception, just like the request method.
|
144
|
+
# On success, returns an OAuth2::Response object.
|
145
|
+
def self.update_group(account, group, options = {})
|
146
|
+
request_for_account(account, :put, "groups/#{group.openstax_uid}",
|
147
|
+
options.merge(
|
148
|
+
body: group.attributes.slice('name', 'is_public').to_json
|
149
|
+
)
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Deletes a group from the Accounts server.
|
154
|
+
# The given account must own the group.
|
155
|
+
# Also takes an options hash.
|
156
|
+
# On failure, throws an Exception, just like the request method.
|
157
|
+
# On success, returns an OAuth2::Response object.
|
158
|
+
def self.destroy_group(account, group, options = {})
|
159
|
+
request_for_account(account, :delete,
|
160
|
+
"groups/#{group.openstax_uid}", options)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Creates a group_member in the Accounts server.
|
164
|
+
# The given account must own the group.
|
165
|
+
# Also takes an options hash.
|
166
|
+
# On failure, throws an Exception, just like the request method.
|
167
|
+
# On success, returns an OAuth2::Response object.
|
168
|
+
def self.create_group_member(account, group_member, options = {})
|
169
|
+
request_for_account(
|
170
|
+
account,
|
171
|
+
:post,
|
172
|
+
"groups/#{group_member.group_id}/members/#{group_member.user_id}",
|
173
|
+
options
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Deletes a group_member from the Accounts server.
|
178
|
+
# The given account must own the group.
|
179
|
+
# Also takes an options hash.
|
180
|
+
# On failure, throws an Exception, just like the request method.
|
181
|
+
# On success, returns an OAuth2::Response object.
|
182
|
+
def self.destroy_group_member(account, group_member, options = {})
|
183
|
+
request_for_account(
|
184
|
+
account,
|
185
|
+
:delete,
|
186
|
+
"groups/#{group_member.group_id}/members/#{group_member.user_id}",
|
187
|
+
options
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Creates a group_owner in the Accounts server.
|
192
|
+
# The given account must own the group.
|
193
|
+
# Also takes an options hash.
|
194
|
+
# On failure, throws an Exception, just like the request method.
|
195
|
+
# On success, returns an OAuth2::Response object.
|
196
|
+
def self.create_group_owner(account, group_owner, options = {})
|
197
|
+
request_for_account(
|
198
|
+
account,
|
199
|
+
:post,
|
200
|
+
"groups/#{group_owner.group_id}/owners/#{group_owner.user_id}",
|
201
|
+
options
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Deletes a group_owner from the Accounts server.
|
206
|
+
# The given account must own the group.
|
207
|
+
# Also takes an options hash.
|
208
|
+
# On failure, throws an Exception, just like the request method.
|
209
|
+
# On success, returns an OAuth2::Response object.
|
210
|
+
def self.destroy_group_owner(account, group_owner, options = {})
|
211
|
+
request_for_account(
|
212
|
+
account,
|
213
|
+
:delete,
|
214
|
+
"groups/#{group_owner.group_id}/owners/#{group_owner.user_id}",
|
215
|
+
options
|
216
|
+
)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Creates a group_nesting in the Accounts server.
|
220
|
+
# The given account must own both groups.
|
221
|
+
# Also takes an an options hash.
|
222
|
+
# On failure, throws an Exception, just like the request method.
|
223
|
+
# On success, returns an OAuth2::Response object.
|
224
|
+
def self.create_group_nesting(account, group_nesting, options = {})
|
225
|
+
request_for_account(
|
226
|
+
account,
|
227
|
+
:post,
|
228
|
+
"groups/#{group_nesting.container_group_id}/nestings/#{
|
229
|
+
group_nesting.member_group_id}",
|
230
|
+
options)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Deletes a group_nesting from the Accounts server.
|
234
|
+
# The given account must own either group.
|
235
|
+
# Also takes an options hash.
|
236
|
+
# On failure, throws an Exception, just like the request method.
|
237
|
+
# On success, returns an OAuth2::Response object.
|
238
|
+
def self.destroy_group_nesting(account, group_nesting, options = {})
|
239
|
+
request_for_account(
|
240
|
+
account,
|
241
|
+
:delete,
|
242
|
+
"groups/#{group_nesting.container_group_id}/nestings/#{
|
243
|
+
group_nesting.member_group_id}",
|
244
|
+
options
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Creates a temporary user in Accounts.
|
249
|
+
# Also takes an options hash.
|
250
|
+
# On failure, throws an Exception, just like the request method.
|
251
|
+
# On success, returns an OAuth2::Response object.
|
252
|
+
def self.create_temp_account(attributes, options = {})
|
253
|
+
request(:post, "user/find-or-create", options.merge(
|
254
|
+
body: attributes.to_json
|
255
|
+
))
|
256
|
+
end
|
257
|
+
|
258
|
+
protected
|
259
|
+
|
260
|
+
def self.client
|
261
|
+
@client ||= OAuth2::Client.new(
|
262
|
+
OpenStax::Accounts.configuration.openstax_application_id,
|
263
|
+
OpenStax::Accounts.configuration.openstax_application_secret,
|
264
|
+
:site => OpenStax::Accounts.configuration.openstax_accounts_url
|
265
|
+
)
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Accounts
|
3
|
+
class Configuration
|
4
|
+
# openstax_accounts_url
|
5
|
+
# Base URL for OpenStax Accounts
|
6
|
+
attr_reader :openstax_accounts_url
|
7
|
+
|
8
|
+
# openstax_application_id
|
9
|
+
# OAuth client_id received from OpenStax Accounts
|
10
|
+
attr_accessor :openstax_application_id
|
11
|
+
|
12
|
+
# openstax_application_secret
|
13
|
+
# OAuth client_secret received from OpenStax Accounts
|
14
|
+
attr_accessor :openstax_application_secret
|
15
|
+
|
16
|
+
# enable_stubbing
|
17
|
+
# Set to true if you want this engine to fake all
|
18
|
+
# interaction with the accounts site.
|
19
|
+
attr_accessor :enable_stubbing
|
20
|
+
|
21
|
+
# logout_via
|
22
|
+
# HTTP method to accept for logout requests
|
23
|
+
attr_accessor :logout_via
|
24
|
+
|
25
|
+
attr_accessor :default_errors_partial
|
26
|
+
attr_accessor :default_errors_html_id
|
27
|
+
attr_accessor :default_errors_added_trigger
|
28
|
+
|
29
|
+
# security_transgression_exception
|
30
|
+
# Class to be used for security transgression exceptions
|
31
|
+
attr_accessor :security_transgression_exception
|
32
|
+
|
33
|
+
# account_user_mapper
|
34
|
+
# This class teaches the gem how to convert between accounts and users
|
35
|
+
# See the "account_user_mapper" discussion in the README
|
36
|
+
attr_accessor :account_user_mapper
|
37
|
+
|
38
|
+
# min_search_characters
|
39
|
+
# The minimum number of characters that can be used
|
40
|
+
# as a query in a call to the AccountsSearch handler
|
41
|
+
# If less are used, the handler will return an error instead
|
42
|
+
attr_accessor :min_search_characters
|
43
|
+
|
44
|
+
# max_search_items
|
45
|
+
# The maximum number of accounts that can be returned
|
46
|
+
# in a call to the AccountsSearch handler
|
47
|
+
# If more would be returned, the result will be empty instead
|
48
|
+
attr_accessor :max_search_items
|
49
|
+
|
50
|
+
def openstax_accounts_url=(url)
|
51
|
+
url.gsub!(/https|http/,'https') if !(url =~ /localhost/)
|
52
|
+
url = url + "/" if url[url.size-1] != '/'
|
53
|
+
@openstax_accounts_url = url
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize
|
57
|
+
@openstax_application_id = 'SET ME!'
|
58
|
+
@openstax_application_secret = 'SET ME!'
|
59
|
+
@openstax_accounts_url = 'https://accounts.openstax.org/'
|
60
|
+
@enable_stubbing = true
|
61
|
+
@logout_via = :get
|
62
|
+
@default_errors_partial = 'openstax/accounts/shared/attention'
|
63
|
+
@default_errors_html_id = 'openstax-accounts-attention'
|
64
|
+
@default_errors_added_trigger = 'openstax-accounts-errors-added'
|
65
|
+
@security_transgression_exception = SecurityTransgression
|
66
|
+
@account_user_mapper = OpenStax::Accounts::DefaultAccountUserMapper
|
67
|
+
@min_search_characters = 3
|
68
|
+
@max_search_items = 10
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
def enable_stubbing?
|
73
|
+
!Rails.env.production? && enable_stubbing
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|