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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -1
  3. data/app/controllers/openstax/accounts/application_controller.rb +1 -1
  4. data/app/models/openstax/accounts/account.rb +11 -6
  5. data/app/models/openstax/accounts/anonymous_account.rb +1 -1
  6. data/app/models/openstax/accounts/application_account.rb +2 -1
  7. data/app/models/openstax/accounts/group.rb +14 -12
  8. data/app/models/openstax/accounts/group_member.rb +10 -10
  9. data/app/models/openstax/accounts/group_nesting.rb +7 -13
  10. data/app/models/openstax/accounts/group_owner.rb +10 -10
  11. data/app/representers/openstax/accounts/api/v1/account_representer.rb +4 -3
  12. data/app/representers/openstax/accounts/api/v1/application_account_representer.rb +7 -2
  13. data/app/representers/openstax/accounts/api/v1/application_accounts_representer.rb +0 -2
  14. data/app/representers/openstax/accounts/api/v1/application_group_representer.rb +7 -2
  15. data/app/representers/openstax/accounts/api/v1/application_groups_representer.rb +0 -2
  16. data/app/representers/openstax/accounts/api/v1/group_nesting_representer.rb +1 -1
  17. data/app/representers/openstax/accounts/api/v1/group_representer.rb +1 -1
  18. data/app/representers/openstax/accounts/api/v1/group_user_representer.rb +1 -1
  19. data/app/routines/openstax/accounts/search_accounts.rb +1 -1
  20. data/app/routines/openstax/accounts/sync_accounts.rb +24 -27
  21. data/app/routines/openstax/accounts/sync_groups.rb +22 -26
  22. data/lib/openstax/accounts/action_controller/base.rb +61 -0
  23. data/lib/openstax/accounts/api.rb +270 -0
  24. data/lib/openstax/accounts/configuration.rb +77 -0
  25. data/lib/openstax/accounts/engine.rb +13 -11
  26. data/lib/openstax/accounts/has_many_through_groups/active_record/base.rb +51 -0
  27. data/lib/openstax/accounts/version.rb +1 -1
  28. data/lib/openstax_accounts.rb +8 -331
  29. data/spec/dummy/app/controllers/api/users_controller.rb +4 -0
  30. data/spec/dummy/config/routes.rb +3 -1
  31. data/spec/dummy/log/test.log +152307 -0
  32. data/spec/lib/openstax/accounts/api_spec.rb +232 -0
  33. data/spec/lib/openstax/accounts/has_many_through_groups/active_record/base_spec.rb +57 -0
  34. data/spec/routines/openstax/accounts/sync_accounts_spec.rb +5 -9
  35. data/spec/routines/openstax/accounts/sync_groups_spec.rb +38 -39
  36. metadata +27 -16
  37. data/lib/openstax/accounts/extend_builtins.rb +0 -50
  38. data/lib/openstax/accounts/has_many_through_groups.rb +0 -47
  39. data/spec/lib/openstax/accounts/has_many_through_groups_spec.rb +0 -53
  40. 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
- begin
21
- OpenStax::Accounts.syncing = true
20
+ return if OpenStax::Accounts.configuration.enable_stubbing?
22
21
 
23
- return if OpenStax::Accounts.configuration.enable_stubbing?
22
+ response = OpenStax::Accounts::Api.get_application_group_updates
24
23
 
25
- response = OpenStax::Accounts.get_application_group_updates
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
- app_groups = []
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
- return if app_groups.empty?
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
- updated_app_groups = []
35
- app_groups.each do |app_group|
36
- group = OpenStax::Accounts::Group.where(
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
- OpenStax::Accounts.mark_group_updates_as_read(updated_app_groups)
52
- ensure
53
- OpenStax::Accounts.syncing = false
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