casino 4.0.3 → 4.1.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 +1 -1
- data/app/assets/stylesheets/casino.scss +60 -1
- data/app/controllers/casino/login_attempts_controller.rb +10 -0
- data/app/controllers/casino/sessions_controller.rb +3 -0
- data/app/helpers/casino/sessions_helper.rb +14 -0
- data/app/models/casino/login_attempt.rb +11 -0
- data/app/models/casino/model_concern/browser_info.rb +14 -0
- data/app/models/casino/ticket_granting_ticket.rb +1 -11
- data/app/models/casino/user.rb +1 -0
- data/app/views/casino/kaminari/_next_page.html.erb +5 -0
- data/app/views/casino/kaminari/_paginator.html.erb +6 -0
- data/app/views/casino/kaminari/_prev_page.html.erb +5 -0
- data/app/views/casino/login_attempts/_table.html.erb +28 -0
- data/app/views/casino/login_attempts/index.html.erb +14 -0
- data/app/views/casino/sessions/index.html.erb +12 -5
- data/casino.gemspec +1 -0
- data/config/locales/de.yml +34 -0
- data/config/locales/en.yml +35 -1
- data/config/locales/fr.yml +16 -0
- data/config/locales/pt-BR.yml +16 -0
- data/config/locales/ru.yml +110 -0
- data/config/locales/zh-CN.yml +16 -0
- data/config/locales/zh-TW.yml +16 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20160502074450_create_login_attempts.rb +14 -0
- data/lib/casino/engine.rb +1 -0
- data/lib/casino/version.rb +1 -1
- data/spec/controllers/login_attempts_controller_spec.rb +35 -0
- data/spec/controllers/sessions_controller_spec.rb +121 -68
- data/spec/dummy/db/schema.rb +11 -2
- data/spec/features/login_attempts_spec.rb +24 -0
- data/spec/features/session_overview_spec.rb +12 -0
- data/spec/model/login_attempt_spec.rb +7 -0
- data/spec/model/ticket_granting_ticket_spec.rb +5 -29
- data/spec/support/factories/login_attempt_factory.rb +10 -0
- data/spec/support/has_browser_info.rb +29 -0
- data/spec/support/kaminari.rb +3 -0
- metadata +38 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51eff1f39a70f787e2addb40bef646e6d3c5d280
|
4
|
+
data.tar.gz: 79bff8cc8bd3f72da3474ff258b6f85c8ad0b4be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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://
|
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,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
|
data/app/models/casino/user.rb
CHANGED
@@ -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 ? '✅' : '❌').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
|
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"> </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
|
-
<%=
|
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>
|
data/casino.gemspec
CHANGED
data/config/locales/de.yml
CHANGED
@@ -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: "« Erster"
|
110
|
+
last: Letzter »
|
111
|
+
next: Nächste Seite
|
112
|
+
previous: Vorherige Seite
|
113
|
+
truncate: "…"
|
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} - %{last}</b> von <b>%{total}</b>
|
data/config/locales/en.yml
CHANGED
@@ -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: "« First"
|
110
|
+
last: "Last »"
|
111
|
+
previous: "‹ Prev"
|
112
|
+
next: "Next ›"
|
113
|
+
truncate: "…"
|
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} - %{last}</b> of <b>%{total}</b> in total"
|