casino 3.0.4 → 4.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +12 -3
  3. data/app/api/casino/api.rb +7 -0
  4. data/app/api/casino/api/entity/auth_token_ticket.rb +5 -0
  5. data/app/api/casino/api/resource/auth_token_tickets.rb +12 -0
  6. data/app/assets/javascripts/casino/{application.js → application.js.erb} +1 -1
  7. data/app/authenticators/casino/static_authenticator.rb +8 -2
  8. data/app/builders/casino/proxy_response_builder.rb +24 -0
  9. data/app/builders/casino/ticket_validation_response_builder.rb +9 -5
  10. data/app/controllers/casino/application_controller.rb +0 -22
  11. data/app/controllers/casino/auth_tokens_controller.rb +34 -0
  12. data/app/controllers/casino/controller_concern/ticket_validator.rb +30 -0
  13. data/app/controllers/casino/proxy_tickets_controller.rb +42 -2
  14. data/app/controllers/casino/service_tickets_controller.rb +15 -2
  15. data/app/controllers/casino/sessions_controller.rb +59 -8
  16. data/app/controllers/casino/two_factor_authenticators_controller.rb +28 -3
  17. data/app/helpers/casino/sessions_helper.rb +75 -0
  18. data/app/helpers/casino/two_factor_authenticators_helper.rb +12 -0
  19. data/app/models/casino/auth_token_ticket.rb +15 -0
  20. data/app/models/casino/login_ticket.rb +7 -4
  21. data/app/models/casino/model_concern/consumable_ticket.rb +20 -0
  22. data/app/models/casino/model_concern/ticket.rb +28 -0
  23. data/app/models/casino/proxy_granting_ticket.rb +12 -0
  24. data/app/models/casino/proxy_ticket.rb +4 -0
  25. data/app/models/casino/service_ticket.rb +5 -4
  26. data/app/models/casino/ticket_granting_ticket.rb +5 -1
  27. data/app/models/casino/two_factor_authenticator.rb +2 -0
  28. data/app/processors/casino/authentication_processor.rb +73 -0
  29. data/app/processors/casino/browser_processor.rb +12 -0
  30. data/app/processors/casino/proxy_granting_ticket_processor.rb +37 -0
  31. data/app/processors/casino/service_ticket_processor.rb +81 -0
  32. data/app/processors/casino/ticket_granting_ticket_processor.rb +56 -0
  33. data/app/processors/casino/two_factor_authenticator_processor.rb +18 -0
  34. data/app/services/casino/auth_token_validation_service.rb +66 -0
  35. data/app/views/casino/sessions/index.html.erb +2 -2
  36. data/app/views/casino/sessions/new.html.erb +1 -1
  37. data/app/views/casino/sessions/validate_otp.html.erb +1 -1
  38. data/app/views/casino/two_factor_authenticators/new.html.erb +6 -3
  39. data/app/views/layouts/application.html.erb +0 -1
  40. data/casino.gemspec +4 -2
  41. data/config/locales/en.yml +35 -0
  42. data/config/locales/zh-CN.yml +88 -0
  43. data/config/locales/zh-TW.yml +88 -0
  44. data/config/routes.rb +3 -10
  45. data/db/migrate/20140831205255_create_auth_token_tickets.rb +10 -0
  46. data/lib/casino.rb +4 -1
  47. data/lib/casino/tasks/cleanup.rake +13 -1
  48. data/lib/casino/version.rb +1 -1
  49. data/spec/controllers/auth_tokens_controller_spec.rb +75 -0
  50. data/spec/controllers/proxy_tickets_controller_spec.rb +120 -14
  51. data/spec/controllers/service_and_proxy_tickets_controller_spec.rb +224 -0
  52. data/spec/controllers/service_tickets_controller_spec.rb +62 -16
  53. data/spec/controllers/sessions_controller_spec.rb +622 -36
  54. data/spec/controllers/two_factor_authenticators_controller_spec.rb +217 -18
  55. data/spec/dummy/config/cas.yml +3 -0
  56. data/spec/dummy/config/environments/development.rb +0 -4
  57. data/spec/dummy/db/migrate/{20130910094259_create_base_models.casino.rb → 20140831214845_create_core_schema.casino.rb} +55 -32
  58. data/spec/dummy/db/migrate/20140831214846_rename_base_models.casino.rb +102 -0
  59. data/spec/dummy/db/migrate/20140831214847_cleanup_indexes.casino.rb +28 -0
  60. data/spec/dummy/db/migrate/20140831214848_fix_long_index_names.casino.rb +13 -0
  61. data/spec/dummy/db/migrate/20140831214849_change_service_to_text.casino.rb +7 -0
  62. data/spec/dummy/db/migrate/20140831214850_change_user_agent_to_text.casino.rb +6 -0
  63. data/spec/dummy/db/migrate/20140831214851_fix_length_of_text_fields.casino.rb +8 -0
  64. data/spec/dummy/db/migrate/20140831214852_create_auth_token_tickets.casino.rb +11 -0
  65. data/spec/dummy/db/schema.rb +79 -70
  66. data/spec/features/login_spec.rb +0 -9
  67. data/spec/model/auth_token_ticket_spec.rb +23 -0
  68. data/spec/services/auth_token_validation_service_spec.rb +83 -0
  69. data/spec/support/sign_in.rb +4 -0
  70. metadata +139 -210
  71. data/app/controllers/casino/api/v1/tickets_controller.rb +0 -55
  72. data/app/helpers/service_tickets_helper.rb +0 -2
  73. data/app/listeners/casino/legacy_validator_listener.rb +0 -11
  74. data/app/listeners/casino/listener.rb +0 -16
  75. data/app/listeners/casino/login_credential_acceptor_listener.rb +0 -38
  76. data/app/listeners/casino/login_credential_requestor_listener.rb +0 -21
  77. data/app/listeners/casino/logout_listener.rb +0 -12
  78. data/app/listeners/casino/other_sessions_destroyer_listener.rb +0 -7
  79. data/app/listeners/casino/proxy_ticket_provider_listener.rb +0 -11
  80. data/app/listeners/casino/second_factor_authentication_acceptor_listener.rb +0 -26
  81. data/app/listeners/casino/session_destroyer_listener.rb +0 -11
  82. data/app/listeners/casino/session_overview_listener.rb +0 -11
  83. data/app/listeners/casino/ticket_validator_listener.rb +0 -11
  84. data/app/listeners/casino/two_factor_authenticator_activator_listener.rb +0 -23
  85. data/app/listeners/casino/two_factor_authenticator_destroyer_listener.rb +0 -16
  86. data/app/listeners/casino/two_factor_authenticator_overview_listener.rb +0 -11
  87. data/app/listeners/casino/two_factor_authenticator_registrator_listener.rb +0 -11
  88. data/app/processors/casino/api/login_credential_acceptor_processor.rb +0 -46
  89. data/app/processors/casino/api/logout_processor.rb +0 -17
  90. data/app/processors/casino/api/service_ticket_provider_processor.rb +0 -69
  91. data/app/processors/casino/legacy_validator_processor.rb +0 -19
  92. data/app/processors/casino/login_credential_acceptor_processor.rb +0 -63
  93. data/app/processors/casino/login_credential_requestor_processor.rb +0 -70
  94. data/app/processors/casino/logout_processor.rb +0 -23
  95. data/app/processors/casino/other_sessions_destroyer_processor.rb +0 -26
  96. data/app/processors/casino/processor.rb +0 -5
  97. data/app/processors/casino/processor_concern/authentication.rb +0 -87
  98. data/app/processors/casino/processor_concern/browser.rb +0 -14
  99. data/app/processors/casino/processor_concern/login_tickets.rb +0 -28
  100. data/app/processors/casino/processor_concern/proxy_granting_tickets.rb +0 -43
  101. data/app/processors/casino/processor_concern/proxy_tickets.rb +0 -56
  102. data/app/processors/casino/processor_concern/service_tickets.rb +0 -50
  103. data/app/processors/casino/processor_concern/ticket_granting_tickets.rb +0 -65
  104. data/app/processors/casino/processor_concern/tickets.rb +0 -17
  105. data/app/processors/casino/processor_concern/two_factor_authenticators.rb +0 -23
  106. data/app/processors/casino/proxy_ticket_provider_processor.rb +0 -41
  107. data/app/processors/casino/proxy_ticket_validator_processor.rb +0 -22
  108. data/app/processors/casino/second_factor_authentication_acceptor_processor.rb +0 -45
  109. data/app/processors/casino/service_ticket_validator_processor.rb +0 -46
  110. data/app/processors/casino/session_destroyer_processor.rb +0 -25
  111. data/app/processors/casino/session_overview_processor.rb +0 -21
  112. data/app/processors/casino/two_factor_authenticator_activator_processor.rb +0 -41
  113. data/app/processors/casino/two_factor_authenticator_destroyer_processor.rb +0 -33
  114. data/app/processors/casino/two_factor_authenticator_overview_processor.rb +0 -20
  115. data/app/processors/casino/two_factor_authenticator_registrator_processor.rb +0 -24
  116. data/spec/controllers/api/v1/tickets_controller_spec.rb +0 -114
  117. data/spec/controllers/listener/legacy_validator_spec.rb +0 -22
  118. data/spec/controllers/listener/login_credential_acceptor_spec.rb +0 -108
  119. data/spec/controllers/listener/login_credential_requestor_spec.rb +0 -57
  120. data/spec/controllers/listener/logout_spec.rb +0 -38
  121. data/spec/controllers/listener/other_sessions_destroyer_spec.rb +0 -19
  122. data/spec/controllers/listener/proxy_ticket_provider_spec.rb +0 -22
  123. data/spec/controllers/listener/second_factor_authentication_acceptor_spec.rb +0 -74
  124. data/spec/controllers/listener/session_destroyer_spec.rb +0 -25
  125. data/spec/controllers/listener/session_overview_spec.rb +0 -26
  126. data/spec/controllers/listener/ticket_validator_spec.rb +0 -22
  127. data/spec/controllers/listener/two_factor_authenticator_activator_spec.rb +0 -64
  128. data/spec/controllers/listener/two_factor_authenticator_destroyer_spec.rb +0 -40
  129. data/spec/controllers/listener/two_factor_authenticator_overview_spec.rb +0 -16
  130. data/spec/controllers/listener/two_factor_authenticator_registrator_spec.rb +0 -27
  131. data/spec/processor/api/login_credential_acceptor_spec.rb +0 -52
  132. data/spec/processor/api/logout_spec.rb +0 -34
  133. data/spec/processor/api/service_ticket_provider_spec.rb +0 -61
  134. data/spec/processor/legacy_validator_spec.rb +0 -78
  135. data/spec/processor/login_credential_acceptor_spec.rb +0 -164
  136. data/spec/processor/login_credential_requestor_spec.rb +0 -145
  137. data/spec/processor/logout_other_sessions_spec.rb +0 -53
  138. data/spec/processor/logout_spec.rb +0 -72
  139. data/spec/processor/processor_concern/service_tickets_spec.rb +0 -49
  140. data/spec/processor/proxy_ticket_provider_spec.rb +0 -66
  141. data/spec/processor/proxy_ticket_validator_spec.rb +0 -65
  142. data/spec/processor/second_factor_authenticaton_acceptor_spec.rb +0 -94
  143. data/spec/processor/session_destroyer_spec.rb +0 -75
  144. data/spec/processor/session_overview_spec.rb +0 -49
  145. data/spec/processor/ticket_validator_spec.rb +0 -214
  146. data/spec/processor/two_factor_authenticator_activator_spec.rb +0 -122
  147. data/spec/processor/two_factor_authenticator_destroyer_spec.rb +0 -71
  148. data/spec/processor/two_factor_authenticator_overview_spec.rb +0 -56
  149. data/spec/processor/two_factor_authenticator_registrator_spec.rb +0 -48
@@ -0,0 +1,56 @@
1
+ module CASino::TicketGrantingTicketProcessor
2
+ extend ActiveSupport::Concern
3
+
4
+ include CASino::BrowserProcessor
5
+
6
+ def find_valid_ticket_granting_ticket(ticket, user_agent, options = {})
7
+ tgt = CASino::TicketGrantingTicket.where(ticket: ticket).first
8
+ unless tgt.nil?
9
+ if tgt.expired?
10
+ Rails.logger.info "Ticket-granting ticket expired (Created: #{tgt.created_at})"
11
+ tgt.destroy
12
+ nil
13
+ elsif !options[:ignore_two_factor] && tgt.awaiting_two_factor_authentication?
14
+ Rails.logger.info 'Ticket-granting ticket is valid, but two-factor authentication is pending'
15
+ nil
16
+ elsif same_browser?(tgt.user_agent, user_agent)
17
+ tgt.user_agent = user_agent
18
+ tgt.touch
19
+ tgt.save!
20
+ tgt
21
+ else
22
+ Rails.logger.info 'User-Agent changed: ticket-granting ticket not valid for this browser'
23
+ nil
24
+ end
25
+ end
26
+ end
27
+
28
+ def acquire_ticket_granting_ticket(authentication_result, user_agent, options = {})
29
+ user_data = authentication_result[:user_data]
30
+ user = load_or_initialize_user(authentication_result[:authenticator], user_data[:username], user_data[:extra_attributes])
31
+ cleanup_expired_ticket_granting_tickets(user)
32
+ user.ticket_granting_tickets.create!({
33
+ awaiting_two_factor_authentication: !user.active_two_factor_authenticator.nil?,
34
+ user_agent: user_agent,
35
+ long_term: !!options[:long_term]
36
+ })
37
+ end
38
+
39
+ def load_or_initialize_user(authenticator, username, extra_attributes)
40
+ user = CASino::User
41
+ .where(authenticator: authenticator, username: username)
42
+ .first_or_initialize
43
+ user.extra_attributes = extra_attributes
44
+ user.save!
45
+ return user
46
+ end
47
+
48
+ def remove_ticket_granting_ticket(ticket_granting_ticket, user_agent)
49
+ tgt = find_valid_ticket_granting_ticket(ticket_granting_ticket, user_agent)
50
+ tgt.destroy unless tgt.nil?
51
+ end
52
+
53
+ def cleanup_expired_ticket_granting_tickets(user)
54
+ CASino::TicketGrantingTicket.cleanup(user)
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ require 'rotp'
2
+
3
+ module CASino::TwoFactorAuthenticatorProcessor
4
+ extend ActiveSupport::Concern
5
+
6
+ def validate_one_time_password(otp, authenticator)
7
+ if authenticator.nil? || authenticator.expired?
8
+ CASino::ValidationResult.new 'INVALID_AUTHENTICATOR', 'Authenticator does not exist or expired', :warn
9
+ else
10
+ totp = ROTP::TOTP.new(authenticator.secret)
11
+ if totp.verify_with_drift(otp, CASino.config.two_factor_authenticator[:drift])
12
+ CASino::ValidationResult.new
13
+ else
14
+ CASino::ValidationResult.new 'INVALID_OTP', 'One-time password not valid', :warn
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,66 @@
1
+ class CASino::AuthTokenValidationService
2
+ include CASino::AuthenticationProcessor
3
+
4
+ AUTH_TOKEN_SIGNERS_GLOB = Rails.root.join('config/auth_token_signers/*.pem').freeze
5
+
6
+ attr_reader :token, :signature
7
+
8
+ def initialize(token, signature)
9
+ @token = token
10
+ @signature = signature
11
+ end
12
+
13
+ def validation_result
14
+ return nil unless user_data
15
+ { authenticator: token_data[:authenticator], user_data: user_data }
16
+ end
17
+
18
+ def user_data
19
+ return @user_data unless @user_data.nil?
20
+ return nil unless signature_valid?
21
+ return nil unless ticket_valid?
22
+ @user_data = load_user_data(token_data[:authenticator], token_data[:username]).tap do |user|
23
+ if user.nil?
24
+ Rails.logger.warn("Could not load user '#{token_data[:authenticator]}'/'#{token_data[:username]}'")
25
+ else
26
+ Rails.logger.info("User '#{token_data[:authenticator]}'/'#{token_data[:username]}' successfully identified through auth token.")
27
+ end
28
+ end
29
+ end
30
+
31
+ def token_data
32
+ begin
33
+ JSON.parse(token).symbolize_keys
34
+ rescue JSON::ParserError
35
+ {}
36
+ end
37
+ end
38
+
39
+ private
40
+ def signature_valid?
41
+ Dir.glob(AUTH_TOKEN_SIGNERS_GLOB) do |path|
42
+ if signature_valid_with_key?(path)
43
+ Rails.logger.info("Successfully validated auth token signature with #{File.basename(path)}")
44
+ return true
45
+ end
46
+ end
47
+ Rails.logger.warn('Signature could not be validated: No matching key found.')
48
+ false
49
+ end
50
+
51
+ def signature_valid_with_key?(path)
52
+ digest = OpenSSL::Digest::SHA256.new
53
+ key = OpenSSL::PKey::RSA.new(File.read(path))
54
+ key.verify(digest, signature, token)
55
+ end
56
+
57
+ def ticket_valid?
58
+ CASino::AuthTokenTicket.consume(token_data[:ticket]).tap do |is_valid|
59
+ Rails.logger.warn('Could not find a valid auth token ticket.') unless is_valid
60
+ end
61
+ end
62
+
63
+ def authentication_service
64
+ @authentication_service ||= CASino::AuthenticationService.new
65
+ end
66
+ end
@@ -4,7 +4,7 @@
4
4
  <div class="info">
5
5
  <h1><%= t('sessions.title') %></h1>
6
6
  <p>
7
- <%= raw t('sessions.currently_logged_in_as', :username => @ticket_granting_tickets[0].user.username) %>
7
+ <%= raw t('sessions.currently_logged_in_as', :username => current_user.username) %>
8
8
  </p>
9
9
 
10
10
  <%= link_to t('sessions.label_logout_button'), logout_path, :class => 'button' %>
@@ -18,7 +18,7 @@
18
18
  <% if @two_factor_authenticators.blank? %>
19
19
  <%= t('two_factor_authenticators.disabled') %> - <%= link_to t('two_factor_authenticators.enable'), new_two_factor_authenticator_path %>
20
20
  <% else %>
21
- <%= t('two_factor_authenticators.enabled') %> - <%= button_to t('two_factor_authenticators.disable'), two_factor_authenticator_path(@two_factor_authenticators[0].id), method: :delete %>
21
+ <%= t('two_factor_authenticators.enabled') %> - <%= button_to t('two_factor_authenticators.disable'), two_factor_authenticator_path(@two_factor_authenticators.first.id), method: :delete %>
22
22
  <% end %>
23
23
 
24
24
  <h3><%= t('sessions.your_active_sessions') %></h3>
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div class="form">
15
15
  <%= form_tag(login_path, method: :post, id: 'login-form') do %>
16
- <%= hidden_field_tag :lt, @login_ticket.ticket %>
16
+ <%= hidden_field_tag :lt, CASino::LoginTicket.create.ticket %>
17
17
  <%= hidden_field_tag :service, params[:service] unless params[:service].nil? %>
18
18
  <%= label_tag :username, t('login.label_username') %>
19
19
  <%= text_field_tag :username, params[:username], autofocus:true %>
@@ -8,7 +8,7 @@
8
8
  <%= hidden_field_tag :tgt, @ticket_granting_ticket || params[:tgt] %>
9
9
  <%= hidden_field_tag :service, params[:service] %>
10
10
  <%= label_tag :code, t('validate_otp.code') %>
11
- <%= text_field_tag :otp, nil, maxlength: 6, autofocus:true %>
11
+ <%= text_field_tag :otp, nil, maxlength: 6, autofocus: true, autocomplete: 'off' %>
12
12
  <%= button_tag t('validate_otp.submit'), :class => 'button' %>
13
13
  <% end %>
14
14
  </div>
@@ -11,10 +11,13 @@
11
11
  <%= t('two_factor_authenticators.instructions') %>
12
12
  </p>
13
13
  <div id="qr-code">
14
- <img src="https://chart.googleapis.com/chart?cht=qr&chs=250x250&chl=<%= u "otpauth://totp/#{u CASino.config.frontend[:sso_name] + ': ' + @two_factor_authenticator.user.username}?secret=#{@two_factor_authenticator.secret}&issuer=#{u CASino.config.frontend[:sso_name]}" %>" height="250" width="250"><br />
14
+ <img src="<%= otp_qr_code_data_url(@two_factor_authenticator) %>" height="250" width="250"><br />
15
15
  </div>
16
16
  <p id="secret">
17
- <%= t('two_factor_authenticators.secret') %>: <%= @two_factor_authenticator.secret %>
17
+ <%= t('two_factor_authenticators.secret') %>:
18
+ <a href="<%= otp_auth_url(@two_factor_authenticator) %>">
19
+ <%= @two_factor_authenticator.secret %>
20
+ </a>
18
21
  </p>
19
22
  </div>
20
23
 
@@ -22,7 +25,7 @@
22
25
  <%= form_tag(two_factor_authenticators_path, method: :post, id: 'two_factor_authenticators-form') do %>
23
26
  <%= hidden_field_tag :id, @two_factor_authenticator.id %>
24
27
  <%= label_tag :code, t('two_factor_authenticators.code') %>
25
- <%= text_field_tag :otp, nil, maxlength: 6, autofocus:true %>
28
+ <%= text_field_tag :otp, nil, maxlength: 6, autofocus: true, autocomplete: 'off' %>
26
29
  <%= link_to t('two_factor_authenticators.cancel'), sessions_path, :class => 'secondary button' %>
27
30
  <%= button_tag t('two_factor_authenticators.submit'), :class => 'button' %>
28
31
  <% end %>
@@ -4,7 +4,6 @@
4
4
  <title><%= CASino.config.frontend[:sso_name] %></title>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
6
  <%= stylesheet_link_tag "application", :media => "all" %>
7
- <%= javascript_tag "window.CASino = { baseUrl: '#{root_path}' };" %>
8
7
  <%= javascript_include_tag "application" %>
9
8
  <%= csrf_meta_tags %>
10
9
  <%= favicon_link_tag 'favicon.png', type: 'image/png' %>
@@ -33,12 +33,14 @@ Gem::Specification.new do |s|
33
33
  s.add_development_dependency 'webmock', '~> 1.9'
34
34
  s.add_development_dependency 'coveralls', '~> 0.7'
35
35
 
36
- s.add_runtime_dependency 'rails', '~> 4.1.0'
36
+ s.add_runtime_dependency 'rails', '>= 4.1.0', '< 4.3.0'
37
37
  s.add_runtime_dependency 'sass-rails', '~> 4.0.0'
38
- s.add_runtime_dependency 'http_accept_language', '~> 2.0.0.pre'
39
38
  s.add_runtime_dependency 'addressable', '~> 2.3'
40
39
  s.add_runtime_dependency 'terminal-table', '~> 1.4'
41
40
  s.add_runtime_dependency 'useragent', '~> 0.4'
42
41
  s.add_runtime_dependency 'faraday', '~> 0.8'
43
42
  s.add_runtime_dependency 'rotp', '~> 2.0'
43
+ s.add_runtime_dependency 'grape', '~> 0.8'
44
+ s.add_runtime_dependency 'grape-entity', '~> 0.4'
45
+ s.add_runtime_dependency 'rqrcode_png', '~> 0.1'
44
46
  end
@@ -51,3 +51,38 @@ en:
51
51
  successfully_deleted: "The two-factor authenticator was successfully deleted."
52
52
  datetime:
53
53
  ago: "%{datetime} ago"
54
+ distance_in_words:
55
+ about_x_hours:
56
+ one: about oue hour
57
+ other: about %{count} hours
58
+ about_x_months:
59
+ one: about one monate
60
+ other: about %{count} monates
61
+ about_x_years:
62
+ one: about one year
63
+ other: about %{count} years
64
+ almost_x_years:
65
+ one: nearly one year
66
+ other: nearly %{count} years
67
+ half_a_minute: half minute
68
+ less_than_x_minutes:
69
+ one: less than one minute
70
+ other: less than %{count} minutes
71
+ less_than_x_seconds:
72
+ one: less than one second
73
+ other: less than %{count} seconds
74
+ over_x_years:
75
+ one: more than one year
76
+ other: more than %{count} years
77
+ x_days:
78
+ one: one day
79
+ other: ! '%{count} days'
80
+ x_minutes:
81
+ one: one minute
82
+ other: ! '%{count} minutes'
83
+ x_months:
84
+ one: one month
85
+ other: ! '%{count} months'
86
+ x_seconds:
87
+ one: one second
88
+ other: ! '%{count} seconds'
@@ -0,0 +1,88 @@
1
+ zh-CN:
2
+ login_credential_acceptor:
3
+ invalid_login_ticket: "您的登录请求没有包含有效的登录授权。"
4
+ invalid_login_credentials: "用户名或密码错误"
5
+ login:
6
+ label_username: "用户名"
7
+ label_password: "密码"
8
+ label_button: "登录"
9
+ label_remember_me: "保持登录"
10
+ notice: ""
11
+ service_not_allowed:
12
+ title: "服务不允许"
13
+ message: "此登录服务器未配置允许登录服务\"%{service}\"。如果您认为此处有误,请联系您的管理员。"
14
+ validate_otp:
15
+ title: "两步验证"
16
+ description: "请输入一次性密码(OTP)."
17
+ code: "编码"
18
+ submit: "继续"
19
+ invalid_otp: "您输入的一次性密码(OTP)无效"
20
+ logout:
21
+ title: "再见"
22
+ logged_out_without_url: "您已经成功登出。"
23
+ logged_out_with_url: "您刚刚成功登出的应用请您点击下面的链接:"
24
+ sessions:
25
+ title: "您好!"
26
+ currently_logged_in_as: "您刚刚以<strong>%{username}</strong>登录。"
27
+ label_logout_button: "登出"
28
+ your_active_sessions: "当前会话"
29
+ table:
30
+ column_browser: "浏览器"
31
+ column_services: "服务"
32
+ column_activity: "最近活动"
33
+ current_session: "当前会话"
34
+ end_session: "结束会话"
35
+ two_factor_authenticators:
36
+ title: "两步验证"
37
+ setup: "设置两步验证"
38
+ description: "两步验证需要您在每次登录时输入一个一次性密码(OTP)。 一次性密码(OTP)可以通过手机应用(比如<a href='http://support.google.com/accounts/bin/answer.py?hl=en&answer=1066447'>Google Authenticator</a> )产生。"
39
+ instructions: "如果您使用google验证器,请用手机扫描下面的二维码,在下方输入验证码。"
40
+ disabled: "当前已关闭"
41
+ enable: "开启"
42
+ enabled: "当前已开启"
43
+ disable: "关闭"
44
+ cancel: "取消"
45
+ secret: "密钥"
46
+ code: "确认码"
47
+ submit: "验证并开启"
48
+ invalid_one_time_password: "一次性密码不正确"
49
+ invalid_two_factor_authenticator: "两步验证已经失效,请按照下面的指示:"
50
+ successfully_activated: "两步验证已经关联到此账户。"
51
+ successfully_deleted: "两步验证已经删除。"
52
+ datetime:
53
+ ago: "%{datetime} 之前"
54
+ distance_in_words:
55
+ about_x_hours:
56
+ one: 大约一小时
57
+ other: 大约%{count}小时
58
+ about_x_months:
59
+ one: 大约一个月
60
+ other: 大约%{count}个月
61
+ about_x_years:
62
+ one: 大约一年
63
+ other: 大约%{count}年
64
+ almost_x_years:
65
+ one: 几乎一年
66
+ other: 几乎%{count}年
67
+ half_a_minute: 半分钟
68
+ less_than_x_minutes:
69
+ one: 少于一分钟
70
+ other: 少于%{count}分钟
71
+ less_than_x_seconds:
72
+ one: 少于一秒
73
+ other: 少于%{count}秒
74
+ over_x_years:
75
+ one: 超过一年
76
+ other: 超过%{count}年
77
+ x_days:
78
+ one: 一天
79
+ other: ! '%{count}天'
80
+ x_minutes:
81
+ one: 一分钟
82
+ other: ! '%{count}分钟'
83
+ x_months:
84
+ one: 一个月
85
+ other: ! '%{count}个月'
86
+ x_seconds:
87
+ one: 一秒
88
+ other: ! '%{count}秒'
@@ -0,0 +1,88 @@
1
+ zh-TW:
2
+ login_credential_acceptor:
3
+ invalid_login_ticket: "您的登錄請求沒有包含有效的登錄授權。"
4
+ invalid_login_credentials: "用戶名或密碼錯誤"
5
+ login:
6
+ label_username: "用戶名"
7
+ label_password: "密碼"
8
+ label_button: "登錄"
9
+ label_remember_me: "保持登錄"
10
+ notice: ""
11
+ service_not_allowed:
12
+ title: "服務不允許"
13
+ message: "此登錄服務器未配置允許登錄服務\"%{service}\"。如果您認為此處有誤,請聯系您的管理員。"
14
+ validate_otp:
15
+ title: "兩步驗證"
16
+ description: "請輸入一次性密碼(OTP)."
17
+ code: "編碼"
18
+ submit: "繼續"
19
+ invalid_otp: "您輸入的一次性密碼(OTP)無效"
20
+ logout:
21
+ title: "再見"
22
+ logged_out_without_url: "您已經成功登出。"
23
+ logged_out_with_url: "您剛剛成功登出的應用請您點擊下面的鏈接:"
24
+ sessions:
25
+ title: "您好!"
26
+ currently_logged_in_as: "您剛剛以<strong>%{username}</strong>登錄。"
27
+ label_logout_button: "登出"
28
+ your_active_sessions: "當前會話"
29
+ table:
30
+ column_browser: "瀏覽器"
31
+ column_services: "服務"
32
+ column_activity: "最近活動"
33
+ current_session: "當前會話"
34
+ end_session: "結束會話"
35
+ two_factor_authenticators:
36
+ title: "兩步驗證"
37
+ setup: "設置兩步驗證"
38
+ description: "兩步驗證需要您在每次登錄時輸入一個一次性密碼(OTP)。 一次性密碼(OTP)可以通過手機應用(比如<a href='http://support.google.com/accounts/bin/answer.py?hl=en&answer=1066447'>Google Authenticator</a> )產生。"
39
+ instructions: "如果您使用google驗證器,請用手機掃描下面的二維碼,在下方輸入驗證碼。"
40
+ disabled: "當前已關閉"
41
+ enable: "開啟"
42
+ enabled: "當前已開啟"
43
+ disable: "關閉"
44
+ cancel: "取消"
45
+ secret: "密鑰"
46
+ code: "確認碼"
47
+ submit: "驗證並開啟"
48
+ invalid_one_time_password: "一次性密碼不正確"
49
+ invalid_two_factor_authenticator: "兩步驗證已經失效,請按照下面的指示:"
50
+ successfully_activated: "兩步驗證已經關聯到此賬戶。"
51
+ successfully_deleted: "兩步驗證已經刪除。"
52
+ datetime:
53
+ ago: "%{datetime} 之前"
54
+ distance_in_words:
55
+ about_x_hours:
56
+ one: 大約一小時
57
+ other: 大約%{count}小時
58
+ about_x_months:
59
+ one: 大約一個月
60
+ other: 大約%{count}個月
61
+ about_x_years:
62
+ one: 大約一年
63
+ other: 大約%{count}年
64
+ almost_x_years:
65
+ one: 幾乎一年
66
+ other: 幾乎%{count}年
67
+ half_a_minute: 半分鐘
68
+ less_than_x_minutes:
69
+ one: 少於一分鐘
70
+ other: 少於%{count}分鐘
71
+ less_than_x_seconds:
72
+ one: 少於一秒
73
+ other: 少於%{count}秒
74
+ over_x_years:
75
+ one: 超過一年
76
+ other: 超過%{count}年
77
+ x_days:
78
+ one: 一天
79
+ other: ! '%{count}天'
80
+ x_minutes:
81
+ one: 一分鐘
82
+ other: ! '%{count}分鐘'
83
+ x_months:
84
+ one: 一個月
85
+ other: ! '%{count}個月'
86
+ x_seconds:
87
+ one: 一秒
88
+ other: ! '%{count}秒'