casino 4.0.3 → 4.1.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/stylesheets/casino.scss +60 -1
  4. data/app/controllers/casino/login_attempts_controller.rb +10 -0
  5. data/app/controllers/casino/sessions_controller.rb +3 -0
  6. data/app/helpers/casino/sessions_helper.rb +14 -0
  7. data/app/models/casino/login_attempt.rb +11 -0
  8. data/app/models/casino/model_concern/browser_info.rb +14 -0
  9. data/app/models/casino/ticket_granting_ticket.rb +1 -11
  10. data/app/models/casino/user.rb +1 -0
  11. data/app/views/casino/kaminari/_next_page.html.erb +5 -0
  12. data/app/views/casino/kaminari/_paginator.html.erb +6 -0
  13. data/app/views/casino/kaminari/_prev_page.html.erb +5 -0
  14. data/app/views/casino/login_attempts/_table.html.erb +28 -0
  15. data/app/views/casino/login_attempts/index.html.erb +14 -0
  16. data/app/views/casino/sessions/index.html.erb +12 -5
  17. data/casino.gemspec +1 -0
  18. data/config/locales/de.yml +34 -0
  19. data/config/locales/en.yml +35 -1
  20. data/config/locales/fr.yml +16 -0
  21. data/config/locales/pt-BR.yml +16 -0
  22. data/config/locales/ru.yml +110 -0
  23. data/config/locales/zh-CN.yml +16 -0
  24. data/config/locales/zh-TW.yml +16 -0
  25. data/config/routes.rb +2 -0
  26. data/db/migrate/20160502074450_create_login_attempts.rb +14 -0
  27. data/lib/casino/engine.rb +1 -0
  28. data/lib/casino/version.rb +1 -1
  29. data/spec/controllers/login_attempts_controller_spec.rb +35 -0
  30. data/spec/controllers/sessions_controller_spec.rb +121 -68
  31. data/spec/dummy/db/schema.rb +11 -2
  32. data/spec/features/login_attempts_spec.rb +24 -0
  33. data/spec/features/session_overview_spec.rb +12 -0
  34. data/spec/model/login_attempt_spec.rb +7 -0
  35. data/spec/model/ticket_granting_ticket_spec.rb +5 -29
  36. data/spec/support/factories/login_attempt_factory.rb +10 -0
  37. data/spec/support/has_browser_info.rb +29 -0
  38. data/spec/support/kaminari.rb +3 -0
  39. metadata +38 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae3e798e52bff45a98cc97f8c3ff311eedd8dd49
4
- data.tar.gz: 1c4c65c4f35d51b3a39834ef57c3a73098e02c04
3
+ metadata.gz: 51eff1f39a70f787e2addb40bef646e6d3c5d280
4
+ data.tar.gz: 79bff8cc8bd3f72da3474ff258b6f85c8ad0b4be
5
5
  SHA512:
6
- metadata.gz: 1c9d475a473c98034e9e1855811816678a9675b0ea289f5e63dc8a3fb080719cacdd351e179e143e2726a4e7ca0d8562c0ff3402c3015be57bb86d608cdc1cea
7
- data.tar.gz: 7201dfd17c106e0cd256ff043335885f5a6e3e4905a098ddba85fd1502c40e421b50953f54d56713093cbbc730d723a9ce0c3c05ffc7e4c226552086b5d62d43
6
+ metadata.gz: eb885c575ed4c09c1aeb767994e05b6859c4a0682dbb5b3a84ae4a3479271b2447faaaf5cdb9641b7d5e1eed763e1d8e1af881c7ecb5bee0c90bc340cf60de21
7
+ data.tar.gz: a4199bd96c19138bc07230598fcd80c36b4b2c2b3f4c9ae3e5d1a8fcc4a4eab4a3dbe9b02bcf03dfc2beab8212b7fa432ff85f4c7e1e5c610e777c69d2e97c5e
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CASino Rails Engine (used in CASinoApp).
4
4
 
5
- It currently supports [CAS 1.0 and CAS 2.0](http://jasig.github.io/cas) as well as [CAS 3.1 Single Sign Out](https://wiki.jasig.org/display/CASUM/Single+Sign+Out).
5
+ It currently supports [CAS 1.0 and CAS 2.0](http://apereo.github.io/cas) as well as [CAS 3.1 Single Sign Out](https://wiki.jasig.org/display/CASUM/Single+Sign+Out).
6
6
 
7
7
  ## Setup
8
8
 
@@ -301,6 +301,35 @@ table {
301
301
  table.tickets {
302
302
  margin-bottom: 10px;
303
303
  }
304
+
305
+ table.login_attempts {
306
+ margin-bottom: 10px;
307
+ }
308
+
309
+ a.all_login_attempts {
310
+ font-size: .95em;
311
+ }
312
+ }
313
+
314
+ /// LOGIN ATTEMPTS ///
315
+ .login_attempts_index {
316
+ width: 800px;
317
+
318
+ h1 {
319
+ float: left;
320
+ }
321
+
322
+ .button {
323
+ float: right;
324
+ }
325
+
326
+ .pagination {
327
+ padding-top: .6em;
328
+ height: 2em;
329
+ .next {
330
+ float: right;
331
+ }
332
+ }
304
333
  }
305
334
 
306
335
  /// LOGOUT ///
@@ -319,6 +348,24 @@ table {
319
348
  }
320
349
 
321
350
 
351
+ @media only screen and (min-width: 800px) {
352
+ th.browser_info {
353
+ width: 16em;
354
+ }
355
+
356
+ th.created_at, th.activity {
357
+ width: 16em;
358
+ }
359
+
360
+ th.actions, th.successful {
361
+ width: 10em;
362
+ }
363
+
364
+ td.successful {
365
+ padding-left: 2em;
366
+ }
367
+ }
368
+
322
369
  /// RESPONSIVE ///
323
370
  @media only screen and (max-width: 600px) {
324
371
  .container {
@@ -329,6 +376,12 @@ table {
329
376
  width: 100%;
330
377
  }
331
378
 
379
+ .login_attempts {
380
+ table {
381
+ padding-top: 100px;
382
+ }
383
+ }
384
+
332
385
  .sessions, .logout {
333
386
  .info {
334
387
  margin-top: 40px;
@@ -366,11 +419,17 @@ table {
366
419
 
367
420
  @media only screen and (max-width: 800px) {
368
421
  .container {
369
- .sessions {
422
+ .sessions, .login_attempts_index {
370
423
  margin-top: 10px;
371
424
  width: 100%;
372
425
  }
373
426
 
427
+ .login_attempts_index {
428
+ table {
429
+ padding-top: 100px;
430
+ }
431
+ }
432
+
374
433
  table, thead, tbody, th, td, tr {
375
434
  display: block;
376
435
  }
@@ -0,0 +1,10 @@
1
+ class CASino::LoginAttemptsController < CASino::ApplicationController
2
+ include CASino::SessionsHelper
3
+
4
+ before_action :ensure_signed_in, only: [:index]
5
+
6
+ def index
7
+ @login_attempts = current_user.login_attempts.order(created_at: :desc)
8
+ .page(params[:page]).per(10)
9
+ end
10
+ end
@@ -11,6 +11,7 @@ class CASino::SessionsController < CASino::ApplicationController
11
11
  def index
12
12
  @ticket_granting_tickets = current_user.ticket_granting_tickets.active
13
13
  @two_factor_authenticators = current_user.two_factor_authenticators.active
14
+ @login_attempts = current_user.login_attempts.order(created_at: :desc).first(5)
14
15
  end
15
16
 
16
17
  def new
@@ -22,6 +23,7 @@ class CASino::SessionsController < CASino::ApplicationController
22
23
  def create
23
24
  validation_result = validate_login_credentials(params[:username], params[:password])
24
25
  if !validation_result
26
+ log_failed_login params[:username]
25
27
  show_login_error I18n.t('login_credential_acceptor.invalid_login_credentials')
26
28
  else
27
29
  sign_in(validation_result, long_term: params[:rememberMe], credentials_supplied: true)
@@ -59,6 +61,7 @@ class CASino::SessionsController < CASino::ApplicationController
59
61
  end
60
62
 
61
63
  private
64
+
62
65
  def show_login_error(message)
63
66
  flash.now[:error] = message
64
67
  render :new, status: :forbidden
@@ -33,6 +33,7 @@ module CASino::SessionsHelper
33
33
 
34
34
  def sign_in(authentication_result, options = {})
35
35
  tgt = acquire_ticket_granting_ticket(authentication_result, request.user_agent, request.remote_ip, options)
36
+ create_login_attempt(tgt.user, true)
36
37
  set_tgt_cookie(tgt)
37
38
  handle_signed_in(tgt, options)
38
39
  end
@@ -50,7 +51,20 @@ module CASino::SessionsHelper
50
51
  cookies.delete :tgt
51
52
  end
52
53
 
54
+ def log_failed_login(username)
55
+ CASino::User.where(username: username).each do |user|
56
+ create_login_attempt(user, false)
57
+ end
58
+ end
59
+
60
+ def create_login_attempt(user, successful)
61
+ user.login_attempts.create! successful: successful,
62
+ user_ip: request.ip,
63
+ user_agent: request.user_agent
64
+ end
65
+
53
66
  private
67
+
54
68
  def handle_signed_in(tgt, options = {})
55
69
  if tgt.awaiting_two_factor_authentication?
56
70
  @ticket_granting_ticket = tgt
@@ -0,0 +1,11 @@
1
+ class CASino::LoginAttempt < ActiveRecord::Base
2
+ include CASino::ModelConcern::BrowserInfo
3
+
4
+ belongs_to :user
5
+
6
+ def username=(username)
7
+ super
8
+
9
+ self.user = CASino::User.find_by_username(username)
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module CASino::ModelConcern::BrowserInfo
2
+ extend ActiveSupport::Concern
3
+
4
+ def browser_info
5
+ unless self.user_agent.blank?
6
+ user_agent = UserAgent.parse(self.user_agent)
7
+ if user_agent.platform.nil?
8
+ "#{user_agent.browser}"
9
+ else
10
+ "#{user_agent.browser} (#{user_agent.platform})"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,6 +2,7 @@ require 'user_agent'
2
2
 
3
3
  class CASino::TicketGrantingTicket < ActiveRecord::Base
4
4
  include CASino::ModelConcern::Ticket
5
+ include CASino::ModelConcern::BrowserInfo
5
6
 
6
7
  self.ticket_prefix = 'TGC'.freeze
7
8
 
@@ -28,17 +29,6 @@ class CASino::TicketGrantingTicket < ActiveRecord::Base
28
29
  tgts.destroy_all
29
30
  end
30
31
 
31
- def browser_info
32
- unless self.user_agent.blank?
33
- user_agent = UserAgent.parse(self.user_agent)
34
- if user_agent.platform.nil?
35
- "#{user_agent.browser}"
36
- else
37
- "#{user_agent.browser} (#{user_agent.platform})"
38
- end
39
- end
40
- end
41
-
42
32
  def same_user?(other_ticket)
43
33
  if other_ticket.nil?
44
34
  false
@@ -4,6 +4,7 @@ class CASino::User < ActiveRecord::Base
4
4
 
5
5
  has_many :ticket_granting_tickets
6
6
  has_many :two_factor_authenticators
7
+ has_many :login_attempts
7
8
 
8
9
  def active_two_factor_authenticator
9
10
  self.two_factor_authenticators.where(active: true).first
@@ -0,0 +1,5 @@
1
+ <span class="next">
2
+ <%= link_to url, rel: 'next', remote: remote do %>
3
+ <%= t('views.pagination.next').html_safe %>
4
+ <% end %>
5
+ </span>
@@ -0,0 +1,6 @@
1
+ <%= paginator.render do -%>
2
+ <nav class="pagination">
3
+ <%= prev_page_tag unless current_page.first? %>
4
+ <%= next_page_tag unless current_page.last? %>
5
+ </nav>
6
+ <% end -%>
@@ -0,0 +1,5 @@
1
+ <span class="prev">
2
+ <%= link_to url, rel: 'prev', remote: remote do %>
3
+ <%= t('views.pagination.previous').html_safe %>
4
+ <% end %>
5
+ </span>
@@ -0,0 +1,28 @@
1
+ <table width="100%" class="login_attempts">
2
+ <thead>
3
+ <tr>
4
+ <th class="browser_info"><%= CASino::LoginAttempt.human_attribute_name(:browser_info) %></th>
5
+ <th class="user_ip"><%= CASino::LoginAttempt.human_attribute_name(:user_ip) %></th>
6
+ <th class="created_at"><%= CASino::LoginAttempt.human_attribute_name(:created_at) %></th>
7
+ <th class="successful"><%= CASino::LoginAttempt.human_attribute_name(:successful) %></th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <% @login_attempts.each do |login_attempt| %>
12
+ <tr>
13
+ <td data-label="<%= CASino::LoginAttempt.human_attribute_name(:browser_info) %>">
14
+ <%= login_attempt.browser_info %>
15
+ </td>
16
+ <td data-label="<%= CASino::LoginAttempt.human_attribute_name(:user_ip) %>">
17
+ <%= login_attempt.user_ip %>
18
+ </td>
19
+ <td data-label="<%= CASino::LoginAttempt.human_attribute_name(:created_at) %>">
20
+ <%= l login_attempt.created_at %>
21
+ </td>
22
+ <td class="successful" data-label="<%= CASino::LoginAttempt.human_attribute_name(:successful) %>">
23
+ <%= (login_attempt.successful ? '&#9989;' : '&#10060;').html_safe %>
24
+ </td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
@@ -0,0 +1,14 @@
1
+ <div class="container">
2
+ <div class="login_attempts_index box">
3
+ <div class="info">
4
+ <h1><%= CASino::LoginAttempt.model_name.human(count: 2) %></h1>
5
+ <%= link_to t('shared.back'), sessions_path, :class => 'button' %>
6
+ </div>
7
+
8
+ <%= render 'table' %>
9
+
10
+ <%= paginate @login_attempts, views_prefix: 'casino' %>
11
+ <%= page_entries_info @login_attempts, entry_name: '' %>
12
+ </div>
13
+ <%= render 'footer' %>
14
+ </div>
@@ -25,10 +25,10 @@
25
25
  <table width="100%" class="tickets">
26
26
  <thead>
27
27
  <tr>
28
- <th><%= t('sessions.table.column_browser') %></th>
29
- <th><%= t('sessions.table.column_services') %></th>
30
- <th><%= t('sessions.table.column_activity') %></th>
31
- <th width="180">&nbsp;</th>
28
+ <th class="browser_info"><%= t('sessions.table.column_browser') %></th>
29
+ <th class="services"><%= t('sessions.table.column_services') %></th>
30
+ <th class="activity"><%= t('sessions.table.column_activity') %></th>
31
+ <th class="actions">&nbsp;</th>
32
32
  </tr>
33
33
  </thead>
34
34
  <tbody>
@@ -43,7 +43,9 @@
43
43
  <%= ticket_granting_ticket.service_tickets.size %>
44
44
  </td>
45
45
  <td data-label="<%= t('sessions.table.column_activity') %>">
46
- <%= t 'datetime.ago', datetime: distance_of_time_in_words_to_now(ticket_granting_ticket.updated_at) %>
46
+ <span title="<%= l ticket_granting_ticket.updated_at %>">
47
+ <%= t 'datetime.ago', datetime: distance_of_time_in_words_to_now(ticket_granting_ticket.updated_at) %>
48
+ </span>
47
49
  </td>
48
50
  <td>
49
51
  <% if current_ticket_granting_ticket?(ticket_granting_ticket) %>
@@ -56,6 +58,11 @@
56
58
  <% end %>
57
59
  </tbody>
58
60
  </table>
61
+
62
+ <h3><%= t('sessions.last_login_attempts') %></h3>
63
+ <%= render partial: 'casino/login_attempts/table' %>
64
+
65
+ <%= link_to t('sessions.all_login_attempts'), login_attempts_path, class: 'all_login_attempts' %>
59
66
  </div>
60
67
  <%= render 'footer' %>
61
68
  </div>
@@ -43,4 +43,5 @@ Gem::Specification.new do |s|
43
43
  s.add_runtime_dependency 'grape', '~> 0.8'
44
44
  s.add_runtime_dependency 'grape-entity', '~> 0.4'
45
45
  s.add_runtime_dependency 'rqrcode_png', '~> 0.1'
46
+ s.add_runtime_dependency 'kaminari', '~> 0.16'
46
47
  end
@@ -32,6 +32,8 @@ de:
32
32
  column_activity: "Letzte Aktivität"
33
33
  current_session: "Aktive Session"
34
34
  end_session: "Session beenden"
35
+ last_login_attempts: Letzte Loginversuche
36
+ all_login_attempts: Alle Loginversuche
35
37
  two_factor_authenticators:
36
38
  title: "Zwei-Faktor-Authentifizierung"
37
39
  setup: "Zwei-Faktor-Authentifizierung einrichten"
@@ -86,3 +88,35 @@ de:
86
88
  x_seconds:
87
89
  one: eine Sekunde
88
90
  other: ! '%{count} Sekunden'
91
+ time:
92
+ formats:
93
+ default: "%Y-%m-%d %H:%M"
94
+ shared:
95
+ back: Zurück
96
+ activerecord:
97
+ attributes:
98
+ casino/login_attempt:
99
+ created_at: Zeitpunkt
100
+ browser_info: Browser
101
+ user_ip: IP-Adresse
102
+ successful: Erfolgreich
103
+ models:
104
+ casino/login_attempt:
105
+ one: Loginversuch
106
+ other: Loginversuche
107
+ views:
108
+ pagination:
109
+ first: "&laquo; Erster"
110
+ last: Letzter &raquo;
111
+ next: Nächste Seite
112
+ previous: Vorherige Seite
113
+ truncate: "&hellip;"
114
+ helpers:
115
+ page_entries_info:
116
+ one_page:
117
+ display_entries:
118
+ one: Zeige <b>1</b> %{entry_name}
119
+ other: Zeige <b>alle %{count}</b> %{entry_name}
120
+ zero: Keine %{entry_name} gefunden
121
+ more_pages:
122
+ display_entries: Zeige %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> von <b>%{total}</b>
@@ -32,6 +32,8 @@ en:
32
32
  column_activity: "Most recent activity"
33
33
  current_session: "Current session"
34
34
  end_session: "End session"
35
+ last_login_attempts: Last Login Attempts
36
+ all_login_attempts: All Login Attempts
35
37
  two_factor_authenticators:
36
38
  title: "Two-factor authentication"
37
39
  setup: "Set up two-factor authentication"
@@ -62,7 +64,7 @@ en:
62
64
  one: about one year
63
65
  other: about %{count} years
64
66
  almost_x_years:
65
- one: nearly one year
67
+ one: nearly one year
66
68
  other: nearly %{count} years
67
69
  half_a_minute: half minute
68
70
  less_than_x_minutes:
@@ -86,3 +88,35 @@ en:
86
88
  x_seconds:
87
89
  one: one second
88
90
  other: ! '%{count} seconds'
91
+ time:
92
+ formats:
93
+ default: "%Y-%m-%d %H:%M"
94
+ shared:
95
+ back: Back
96
+ activerecord:
97
+ attributes:
98
+ casino/login_attempt:
99
+ created_at: Time
100
+ browser_info: Browser
101
+ user_ip: IP Address
102
+ successful: Successful
103
+ models:
104
+ casino/login_attempt:
105
+ one: Login Attempt
106
+ other: Login Attempts
107
+ views:
108
+ pagination:
109
+ first: "&laquo; First"
110
+ last: "Last &raquo;"
111
+ previous: "&lsaquo; Prev"
112
+ next: "Next &rsaquo;"
113
+ truncate: "&hellip;"
114
+ helpers:
115
+ page_entries_info:
116
+ one_page:
117
+ display_entries:
118
+ zero: "No %{entry_name} found"
119
+ one: "Displaying <b>1</b> %{entry_name}"
120
+ other: "Displaying <b>all %{count}</b> %{entry_name}"
121
+ more_pages:
122
+ display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"