casino 4.0.3 → 4.1.0

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