openstax_accounts 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +20 -0
  3. data/app/controllers/openstax/accounts/dev/base_controller.rb +19 -0
  4. data/app/controllers/openstax/accounts/dev/users_controller.rb +10 -9
  5. data/app/handlers/openstax/accounts/dev/users_index.rb +39 -0
  6. data/app/handlers/openstax/accounts/sessions_omniauth_authenticated.rb +2 -1
  7. data/app/models/openstax/accounts/application_user.rb +7 -0
  8. data/app/models/openstax/accounts/user.rb +15 -3
  9. data/app/representers/openstax/accounts/api/v1/application_user_representer.rb +5 -1
  10. data/app/representers/openstax/accounts/api/v1/application_user_search_representer.rb +19 -0
  11. data/app/representers/openstax/accounts/api/v1/application_users_representer.rb +16 -0
  12. data/app/representers/openstax/accounts/api/v1/user_representer.rb +2 -6
  13. data/app/routines/openstax/accounts/dev/create_user.rb +4 -4
  14. data/app/routines/openstax/accounts/dev/search_users.rb +27 -0
  15. data/app/routines/openstax/accounts/search_users.rb +174 -32
  16. data/app/routines/openstax/accounts/sync_users.rb +44 -0
  17. data/app/views/openstax/accounts/dev/users/_search_results.html.erb +50 -0
  18. data/app/views/openstax/accounts/dev/users/index.js.erb +3 -0
  19. data/app/views/openstax/accounts/dev/users/login.html.erb +6 -5
  20. data/app/views/openstax/accounts/shared/_attention.html.erb +1 -1
  21. data/app/views/openstax/accounts/shared/users/_index.html.erb +24 -0
  22. data/config/routes.rb +5 -4
  23. data/lib/generators/openstax/accounts/schedule/USAGE +8 -0
  24. data/lib/generators/openstax/accounts/schedule/schedule_generator.rb +18 -0
  25. data/lib/generators/openstax/accounts/schedule/templates/schedule.rb +3 -0
  26. data/lib/omniauth/strategies/openstax.rb +1 -1
  27. data/lib/openstax/accounts/engine.rb +2 -0
  28. data/lib/openstax/accounts/version.rb +1 -1
  29. data/lib/openstax_accounts.rb +69 -26
  30. data/spec/controllers/openstax/accounts/dev/users_controller_spec.rb +4 -3
  31. data/spec/dummy/app/controllers/api/application_users_controller.rb +12 -0
  32. data/spec/dummy/config/application.rb +3 -0
  33. data/spec/dummy/config/initializers/openstax_accounts.rb +1 -0
  34. data/spec/dummy/config/routes.rb +5 -4
  35. data/spec/lib/openstax_accounts_spec.rb +19 -11
  36. data/spec/routines/openstax/accounts/dev/create_user_spec.rb +26 -0
  37. data/spec/routines/openstax/accounts/search_users_spec.rb +129 -0
  38. metadata +47 -68
  39. data/app/controllers/openstax/accounts/dev/dev_controller.rb +0 -13
  40. data/app/handlers/openstax/accounts/dev/users_search.rb +0 -38
  41. data/app/representers/openstax/accounts/api/v1/contact_info_representer.rb +0 -23
  42. data/app/representers/openstax/accounts/api/v1/email_address_representer.rb +0 -9
  43. data/app/views/openstax/accounts/dev/users/search.js.erb +0 -21
  44. data/app/views/openstax/accounts/users/_action_create_form.html.erb +0 -9
  45. data/app/views/openstax/accounts/users/_action_dialog.html.erb +0 -10
  46. data/app/views/openstax/accounts/users/_action_list.html.erb +0 -33
  47. data/app/views/openstax/accounts/users/_action_search.html.erb +0 -25
  48. data/app/views/openstax/accounts/users/action_search.js.erb +0 -8
  49. data/spec/dummy/app/controllers/api/users_controller.rb +0 -7
@@ -0,0 +1,44 @@
1
+ # Routine for getting user updates from Accounts
2
+ #
3
+ # Should be scheduled to run regularly
4
+
5
+ module OpenStax
6
+ module Accounts
7
+
8
+ class SyncUsers
9
+
10
+ SYNC_ATTRIBUTES = ['username', 'first_name', 'last_name',
11
+ 'full_name', 'title']
12
+
13
+ lev_routine transaction: :no_transaction
14
+
15
+ protected
16
+
17
+ def exec(options={})
18
+
19
+ return if OpenStax::Accounts.configuration.enable_stubbing?
20
+
21
+ response = OpenStax::Accounts.application_users_updates
22
+
23
+ app_users = []
24
+ app_users_rep = OpenStax::Accounts::Api::V1::ApplicationUsersRepresenter.new(app_users)
25
+ app_users_rep.from_json(response.body)
26
+
27
+ return if app_users.empty?
28
+
29
+ app_users_hash = {}
30
+ app_users.each do |app_user|
31
+ user = OpenStax::Accounts::User.where(:openstax_uid => app_user.user.openstax_uid).first
32
+ user.updating_from_accounts = true
33
+ next unless user.update_attributes(app_user.user.attributes.slice(*SYNC_ATTRIBUTES))
34
+ app_users_hash[app_user.id] = app_user.unread_updates
35
+ end
36
+
37
+ OpenStax::Accounts.application_users_updated(app_users_hash)
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ <%
2
+ page = @handler_result.outputs[:page]
3
+ num_users = @handler_result.outputs[:num_matching_users]
4
+ per_page = @handler_result.outputs[:per_page]
5
+ pages = (num_users * 1.0 / per_page).ceil
6
+ users = @handler_result.outputs[:users]
7
+ %>
8
+
9
+ <div id='search-results-pagination'>
10
+ <%= pluralize(num_users, 'user') %> found.
11
+
12
+ <% if pages > 0 %>
13
+ Page:
14
+
15
+ <%
16
+ linked_page_numbers = [page+1]
17
+ linked_page_numbers.push(1)
18
+ linked_page_numbers.push(pages)
19
+ linked_page_numbers.push(page+1-1, page+1-2, page+1+1, page+1+2)
20
+ linked_page_numbers.reject!{|pp| pp < 1 || pp > pages}
21
+ linked_page_numbers.uniq!
22
+ linked_page_numbers.sort!
23
+ %>
24
+
25
+ <% linked_page_numbers.each do |lpn| %>
26
+ <%= link_to_unless lpn == page + 1,
27
+ lpn,
28
+ dev_users_path(search: {terms: @handler_result.outputs[:query], page: lpn-1, per_page: per_page}), remote: true %>
29
+ <% end %>
30
+ <% end %>
31
+ </div>
32
+
33
+ <%= osu.action_list(
34
+ records: users,
35
+ list: {
36
+ headings: ['Username', 'First Name', 'Last Name', ''],
37
+ widths: ['25%', '25%', '25%', '25%'],
38
+ data_procs:
39
+ [
40
+ Proc.new { |user| user.username },
41
+ Proc.new { |user| user.first_name || '---' },
42
+ Proc.new { |user| user.last_name || '---' },
43
+ Proc.new { |user|
44
+ link_to 'Sign in as',
45
+ become_dev_user_path(user),
46
+ method: :post
47
+ }
48
+ ]
49
+ }
50
+ ) %>
@@ -0,0 +1,3 @@
1
+ <%= unless_errors alerts_html_id: 'dialog-local-alerts' do %>
2
+ $("#search-results-list").html("<%= j(render 'openstax/accounts/dev/users/search_results') %>");
3
+ <% end %>
@@ -1,4 +1,3 @@
1
-
2
1
  <div class="openstax-accounts development-login">
3
2
 
4
3
  <% handler_errors.each do |error| %>
@@ -7,11 +6,13 @@
7
6
 
8
7
  <%= osu.section_block "Development Login" do %>
9
8
 
10
- <p>You need to login, but we're not connected to the Accounts server. Search for a user below
11
- and click the sign in link next to him or her.</p>
9
+ <p>You need to login, but we're not connected to the Accounts server.
10
+ Search for a user below and click the sign in link next to him or her.</p>
12
11
 
13
- <%= render 'openstax/accounts/users/action_search', action_search_path: search_dev_users_path %>
12
+ <%= render 'openstax/accounts/shared/users/index',
13
+ :search_action_path => openstax_accounts.dev_users_path,
14
+ :remote => true %>
14
15
 
15
16
  <% end %>
16
17
 
17
- </div>
18
+ </div>
@@ -1,3 +1,3 @@
1
1
  <% handler_errors.each do |error| %>
2
2
  <%= error.translate %>
3
- <% end %>
3
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <%
2
+ # Clients of this partial can override the following variables:
3
+ search_action_path ||= nil
4
+ remote ||= false
5
+ form_html ||= {id: 'search-form',
6
+ class: 'form-inline'}
7
+ search_types ||= ['Any', 'Username', 'Email']
8
+ %>
9
+
10
+ <%= lev_form_for :search,
11
+ url: search_action_path,
12
+ remote: remote,
13
+ html: form_html do |f| %>
14
+
15
+ Search for
16
+ <%= f.search_field :terms, style: 'width:300px' %>
17
+ in
18
+ <%= f.select :type, search_types, {}, {style: 'width: 150px'} %>
19
+
20
+ <%= f.submit 'Search', class: 'btn btn-primary' %>
21
+
22
+ <% end %>
23
+
24
+ <div id="search-results-list"></div>
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  OpenStax::Accounts::Engine.routes.draw do
2
- match '/auth/openstax/callback', to: 'sessions#omniauth_authenticated' #omniauth route
2
+ get '/auth/openstax/callback', to: 'sessions#omniauth_authenticated' #omniauth route
3
3
  get '/auth/openstax', :as => 'openstax_login'
4
4
 
5
5
  get 'sessions/new', :as => 'login'
@@ -9,12 +9,13 @@ OpenStax::Accounts::Engine.routes.draw do
9
9
 
10
10
  if OpenStax::Accounts.configuration.enable_stubbing?
11
11
  namespace :dev do
12
- resources :users, :only => [] do
12
+ resources :users, :only => [:index] do
13
13
  collection do
14
14
  get 'login'
15
- post 'search'
16
- post 'become'
15
+ post 'index'
17
16
  end
17
+
18
+ post 'become', :on => :member
18
19
  end
19
20
  end
20
21
  end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Creates a "whenever" task that checks for updates from Accounts.
3
+
4
+ Example:
5
+ rails generate openstax:accounts:schedule
6
+
7
+ This will create or append to:
8
+ config/schedule.rb
@@ -0,0 +1,18 @@
1
+ # Can't use OpenStax. See https://github.com/rails/rails/issues/13856
2
+ module Openstax
3
+ module Accounts
4
+ class ScheduleGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def generate_schedule
8
+ if File.exists?(File.expand_path('config/schedule.rb'))
9
+ File.open(File.expand_path('../templates/schedule.rb', __FILE__)) do |file|
10
+ append_file 'config/schedule.rb', file.read
11
+ end
12
+ else
13
+ copy_file 'schedule.rb', 'config/schedule.rb'
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ every 5.minutes do
2
+ runner "OpenStax::Accounts::SyncUsers.call"
3
+ end
@@ -32,7 +32,7 @@ module OmniAuth
32
32
  end
33
33
 
34
34
  def raw_info
35
- @raw_info ||= access_token.get('/api/users/me.json').parsed
35
+ @raw_info ||= access_token.get('/api/user.json').parsed
36
36
  end
37
37
  end
38
38
  end
@@ -3,6 +3,8 @@ require 'omniauth/strategies/openstax'
3
3
  require 'lev'
4
4
  require 'roar/decorator'
5
5
  require 'roar/representer/json'
6
+ require 'keyword_search'
7
+ require 'squeel'
6
8
 
7
9
  ActiveSupport::Inflector.inflections do |inflect|
8
10
  inflect.acronym 'OpenStax'
@@ -1,5 +1,5 @@
1
1
  module OpenStax
2
2
  module Accounts
3
- VERSION = "0.3.0"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -6,12 +6,14 @@ require 'openstax/accounts/user_provider'
6
6
  require 'openstax/accounts/current_user_manager'
7
7
 
8
8
  require 'openstax_utilities'
9
- require 'uri'
10
9
  require 'oauth2'
10
+ require 'uri'
11
11
 
12
12
  module OpenStax
13
13
  module Accounts
14
14
 
15
+ DEFAULT_API_VERSION = :v1
16
+
15
17
  class << self
16
18
 
17
19
  ###########################################################################
@@ -27,9 +29,6 @@ module OpenStax
27
29
  # ...
28
30
  # end
29
31
  #
30
- # Set enable_stubbing to true iff you want this engine to fake all
31
- # interaction with the accounts site.
32
- #
33
32
 
34
33
  def configure
35
34
  yield configuration
@@ -40,26 +39,50 @@ module OpenStax
40
39
  end
41
40
 
42
41
  class Configuration
42
+ # openstax_accounts_url
43
+ # Base URL for OpenStax Accounts
44
+ attr_reader :openstax_accounts_url
45
+
46
+ # openstax_application_id
47
+ # OAuth client_id received from OpenStax Accounts
43
48
  attr_accessor :openstax_application_id
49
+
50
+ # openstax_application_secret
51
+ # OAuth client_secret received from OpenStax Accounts
44
52
  attr_accessor :openstax_application_secret
53
+
54
+ # enable_stubbing
55
+ # Set to true if you want this engine to fake all
56
+ # interaction with the accounts site.
45
57
  attr_accessor :enable_stubbing
46
- attr_reader :openstax_accounts_url
58
+
59
+ # logout_via
60
+ # HTTP method to accept for logout requests
47
61
  attr_accessor :logout_via
62
+
48
63
  attr_accessor :default_errors_partial
49
64
  attr_accessor :default_errors_html_id
50
65
  attr_accessor :default_errors_added_trigger
66
+
67
+ # security_transgression_exception
68
+ # Class to be used for security transgression exceptions
51
69
  attr_accessor :security_transgression_exception
52
70
 
53
- # See the "user_provider" discussion in the README
71
+ # See the "user_provider" discussion in the README
54
72
  attr_accessor :user_provider
55
73
 
74
+ # The maximum number of users that can be returned in a call to SearchUsers
75
+ # If more would be returned, the result will be empty instead
76
+ # Can also be passed directly to SearchUsers
77
+ attr_accessor :max_matching_users
78
+
56
79
  def openstax_accounts_url=(url)
57
80
  url.gsub!(/https|http/,'https') if !(url =~ /localhost/)
58
81
  url = url + "/" if url[url.size-1] != '/'
59
82
  @openstax_accounts_url = url
60
83
  end
61
84
 
62
- def initialize
85
+ def initialize
63
86
  @openstax_application_id = 'SET ME!'
64
87
  @openstax_application_secret = 'SET ME!'
65
88
  @openstax_accounts_url = 'https://accounts.openstax.org/'
@@ -70,6 +93,7 @@ module OpenStax
70
93
  @default_errors_added_trigger = 'openstax-accounts-errors-added'
71
94
  @security_transgression_exception = SecurityTransgression
72
95
  @user_provider = OpenStax::Accounts::UserProvider
96
+ @max_matching_users = 10
73
97
  super
74
98
  end
75
99
 
@@ -96,43 +120,62 @@ module OpenStax
96
120
 
97
121
  token_string = options.delete(:access_token)
98
122
  token = token_string.blank? ? client.client_credentials.get_token :
99
- OAuth2::AccessToken.new(client, token_string)
123
+ OAuth2::AccessToken.new(client, token_string)
100
124
 
101
125
  api_url = URI.join(configuration.openstax_accounts_url, 'api/', url)
102
126
 
103
127
  token.request(http_method, api_url, options)
104
128
  end
105
129
 
106
- # Creates an ApplicationUser in Accounts for the configured app
107
- # and the given OpenStax::Accounts::User.
108
- # Also takes an optional API version parameter. Defaults to :v1.
130
+ # Performs an ApplicationUser search in Accounts for the configured app.
131
+ # Takes a query parameter and an optional API version parameter.
132
+ # API version currently defaults to :v1 (may change in the future).
109
133
  # On failure, throws an Exception, just like api_call.
110
134
  # On success, returns an OAuth2::Response object.
111
- def create_application_user(user, version = :v1)
112
- options = {:access_token => user.access_token,
135
+ def application_users_index(query, version = DEFAULT_API_VERSION)
136
+ options = {:params => {:q => query},
113
137
  :api_version => version}
114
- api_call(:post, 'application_users', options)
138
+ api_call(:get, 'application_users', options)
115
139
  end
116
140
 
117
- # Performs a user search in Accounts for the configured app.
118
- # Takes a query parameter and an optional API version parameter.
119
- # API version currently defaults to :v1.
141
+ # Retrieves information about ApplicationUsers that have been recently updated.
120
142
  # On failure, throws an Exception, just like api_call.
121
143
  # On success, returns an OAuth2::Response object.
122
- def user_search(query, version = :v1)
123
- options = {:params => {:q => query},
124
- :api_version => version}
125
- api_call(:get, 'users/search', options)
144
+ def application_users_updates(version = DEFAULT_API_VERSION)
145
+ options = {:api_version => version}
146
+ api_call(:get, 'application_users/updates', options)
147
+ end
148
+
149
+ # Marks ApplicationUser updates as "read".
150
+ # The app_users parameter is a hash that maps ApplicationUser
151
+ # openstax_uid's to the value of the last received unread_updates.
152
+ # On failure, throws an Exception, just like api_call.
153
+ # On success, returns an OAuth2::Response object.
154
+ def application_users_updated(app_users, version = DEFAULT_API_VERSION)
155
+ options = {:api_version => version,
156
+ :body => {:application_users => app_users}}
157
+ api_call(:put, 'application_users/updated', options)
158
+ end
159
+
160
+ # Updates an OpenStax::Accounts::User in Accounts for the configured app.
161
+ # Also takes an optional API version parameter.
162
+ # API version currently defaults to :v1 (may change in the future).
163
+ # On failure, throws an Exception, just like api_call.
164
+ # On success, returns an OAuth2::Response object.
165
+ def user_update(user, version = DEFAULT_API_VERSION)
166
+ options = {:access_token => user.access_token,
167
+ :api_version => version,
168
+ :body => user.attributes.slice('username', 'first_name',
169
+ 'last_name', 'full_name', 'title').to_json}
170
+ api_call(:put, 'user', options)
126
171
  end
127
172
 
128
- protected
173
+ protected
129
174
 
130
175
  def client
131
- @client ||= OAuth2::Client.new(
132
- configuration.openstax_application_id,
176
+ @client ||= OAuth2::Client.new(configuration.openstax_application_id,
133
177
  configuration.openstax_application_secret,
134
- :site => configuration.openstax_accounts_url
135
- )
178
+ :site => configuration.openstax_accounts_url)
136
179
  end
137
180
 
138
181
  end
@@ -5,13 +5,14 @@ module OpenStax::Accounts
5
5
  describe UsersController do
6
6
  routes { OpenStax::Accounts::Engine.routes }
7
7
 
8
- let!(:user) { OpenStax::Accounts::User.create(username: 'some_user',
9
- openstax_uid: 1) }
8
+ let!(:user) { user = FactoryGirl.create :openstax_accounts_user,
9
+ username: 'some_user',
10
+ openstax_uid: 10 }
10
11
 
11
12
  it 'should allow users not in production to become other users' do
12
13
  expect(controller.current_user).to eq(OpenStax::Accounts::User.anonymous)
13
14
  expect(controller.current_user.is_anonymous?).to eq(true)
14
- get :become, user_id: user.id
15
+ post :become, id: user.id
15
16
  expect(controller.current_user).to eq(user)
16
17
  expect(controller.current_user.is_anonymous?).to eq(false)
17
18
  end
@@ -1,7 +1,19 @@
1
1
  module Api
2
2
  class ApplicationUsersController < DummyController
3
+ def index
4
+ dummy(:index)
5
+ end
6
+
3
7
  def create
4
8
  dummy(:create)
5
9
  end
10
+
11
+ def updates
12
+ dummy(:updates)
13
+ end
14
+
15
+ def updated
16
+ dummy(:updated)
17
+ end
6
18
  end
7
19
  end