cassy 1.1.4 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +156 -0
- data/Rakefile +17 -1
- data/lib/cassy/authenticators/base.rb +6 -2
- data/lib/cassy/authenticators/devise.rb +13 -5
- data/lib/cassy/authenticators/test.rb +8 -1
- data/lib/cassy/cas.rb +109 -192
- data/lib/cassy/engine.rb +3 -5
- data/lib/cassy/routes.rb +4 -4
- data/lib/cassy/utils.rb +1 -2
- data/lib/cassy/version.rb +3 -0
- metadata +156 -103
- data/README.markdown +0 -70
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d02c0cb8713326b7d31872a36f9c654d0e6029f9
|
4
|
+
data.tar.gz: b31c080549d0595d9a442b73facffb3711e8143e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 59d9cb6a3fcf836fa1dc313bfb1673b2f575767a07736cb03486b78e3e4f111610a8cc227b18d16965d0fc6ec0f06becf8db20974d2c79d437846d2ab8b7744c
|
7
|
+
data.tar.gz: 5f2720661a41e3f6d43bb30cca09988e9f839b0829a5fdecdadbc884e62c9fc31e6ba97068bd50d69934a8862245b49fb22627c4404b47bea2f43ccef987b05d
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# Cassy
|
2
|
+
|
3
|
+
This project is designed to be a Rails engine that uses a large portion of the code from the [rubycas-server][https://github.com/gunark/rubycas-server] project. Certain portions of this code belong to the rubycas-server project owners.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
This engine currently only works with Rails 3 and above. To have it work with the application you must do four things:
|
8
|
+
|
9
|
+
1. Put this line in your project's `Gemfile`:
|
10
|
+
|
11
|
+
```
|
12
|
+
gem 'cassy'
|
13
|
+
```
|
14
|
+
|
15
|
+
2. Create a configuration file at `config/cassy.yml` and fill it with these values:
|
16
|
+
|
17
|
+
```
|
18
|
+
# Times are in seconds.
|
19
|
+
maximum_unused_login_ticket_lifetime: 300
|
20
|
+
maximum_unused_service_ticket_lifetime: 300
|
21
|
+
|
22
|
+
authenticator:
|
23
|
+
class: Cassy::Authenticators::Devise
|
24
|
+
```
|
25
|
+
|
26
|
+
The first two keys are the time-to-expiry for the login and service tickets respectively. The class for the authentication can be any constant which responds to a `validates` method. Only Devise authentication is supported at the moment.
|
27
|
+
|
28
|
+
3. Create a new initializer (probably called `config/initializers/cassy.rb`) and point cassy at the configuration file for your application:
|
29
|
+
|
30
|
+
```
|
31
|
+
Cassy::Engine.config.config_file = Rails.root + "config/cassy.yml"
|
32
|
+
```
|
33
|
+
|
34
|
+
4. Tell Cassy to load its routes in your application by calling `cassy` in `config/routes.rb`:
|
35
|
+
|
36
|
+
```
|
37
|
+
Rails.application.routes.draw do
|
38
|
+
cassy
|
39
|
+
|
40
|
+
# your routes go here
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Boom, done. Now this application will act as a CAS server.
|
45
|
+
|
46
|
+
For customization options please see the "Customization" section below.
|
47
|
+
|
48
|
+
## Configuration
|
49
|
+
|
50
|
+
The configuration options for this gem goes into a file called `config/cassy.yml` at the root of the project if you've set it up as advised, and this allows the engine to be configured.
|
51
|
+
|
52
|
+
These configuration options are detailed here for your convenience. For specific term definitions, please consult the CAS spec.
|
53
|
+
|
54
|
+
* `authenticator`: Must specify at least one key, `class`, which is a string version of a constant that will be used for authentication in the system. This constant *must* respond to `validate`.
|
55
|
+
* `maximum_unused_login_ticket_lifetime`: The time before a login ticket would expire.
|
56
|
+
* `maximum_unused_service_ticket_lifetime`: The time before a service ticket would expire.
|
57
|
+
* `username_field`: Defines the field on the users table which is used for the lookup for the username. Defaults to " username".
|
58
|
+
* `username_label`: Allows for the "Username" label on the sign in page to be given a different value. Helpful if you want to call it "Email" or "User Name" instead.
|
59
|
+
* `client_app_user_field`: Defines the field name for the username on the *client* application side.
|
60
|
+
* `service_list`: List of services that use this server to authenticate, separated by environment.
|
61
|
+
* `default_redirect_url`: If the requested service isn't in the service_list (or is blank) then tickets will be generated for the valid services then the user will be redirected to here. Needs to be specified per environment as per the sample below. The default_redirect_url needs to be on the same domain as (at least) one of the urls on the service_list.
|
62
|
+
* `loosely_match_services`: If this is set to true, a request for the service http://www.something.com/something_else can be matched to the ticket for http://www.something.com.
|
63
|
+
* `enable_single_sign_out`: If this is set to true, calling send_logout_notification on a service ticket will send a request to the service telling it to clear the associated users session. Calling destroy_and_logout_all_service_tickets on a ticket granting ticket will send a session-terminating request to each service before destroying itself.
|
64
|
+
* `no_concurrent_sessions`: (requires enable_single_sign_out to be true) If this is true, when someone logs in, a session-terminating request is sent to each service for any old service tickets related to the current user.
|
65
|
+
* `concurrent_session_types`: If no_concurrent_sessions is true, concurrent_session_types can be specified so that a user can have concurrent sessions on different device types. If enabled, override `session_type` in `SessionsController` to return the session_type (any string).
|
66
|
+
|
67
|
+
A sample `cassy.yml` file:
|
68
|
+
|
69
|
+
```
|
70
|
+
maximum_unused_login_ticket_lifetime: 7200
|
71
|
+
maximum_unused_service_ticket_lifetime: 7200
|
72
|
+
maximum_session_lifetime: 7200
|
73
|
+
username_field: username
|
74
|
+
client_app_user_field: id
|
75
|
+
service_list:
|
76
|
+
production:
|
77
|
+
- https://agencieshq.com/users/service
|
78
|
+
- https://administratorshq.agencieshq.com/users/service
|
79
|
+
development:
|
80
|
+
- http://localhost:3000/users/service
|
81
|
+
- http://localhost:3001/users/service
|
82
|
+
- http://localhost:3002/users/service
|
83
|
+
default_redirect_url:
|
84
|
+
development: http://localhost:3000
|
85
|
+
production: http://www.something.com
|
86
|
+
loosely_match_services: true
|
87
|
+
authenticator:
|
88
|
+
class: Cassy::Authenticators::Devise
|
89
|
+
no_concurrent_sessions: true
|
90
|
+
concurrent_session_types: [:mobile, :desktop]
|
91
|
+
extra_attributes:
|
92
|
+
- user_id
|
93
|
+
- user_username
|
94
|
+
```
|
95
|
+
|
96
|
+
## Customization
|
97
|
+
|
98
|
+
### Sessions Controller
|
99
|
+
|
100
|
+
In Cassy, it is possible to override the controller which is used for authentication. To do this, the controller can be configured in `config/routes.rb`:
|
101
|
+
|
102
|
+
```
|
103
|
+
cassy :controllers => "sessions"
|
104
|
+
```
|
105
|
+
|
106
|
+
By doing this, it will point at the `SessionsController` rather than the default of `Cassy::SessionsController`. This controller then should inherit from `Cassy::SessionsController` to inherit the original behaviour and will need to point to the views of Cassy:
|
107
|
+
|
108
|
+
```
|
109
|
+
class SessionsController < Cassy::SessionsController
|
110
|
+
def new
|
111
|
+
# custom behaviour goes here
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
## Contributing
|
118
|
+
|
119
|
+
### Versioning
|
120
|
+
|
121
|
+
We use [Semantic Versioning](http://semver.org/) for our open source dependencies, and a modified version of Semantic Versioning in our internal dependencies.
|
122
|
+
|
123
|
+
#### Open Source
|
124
|
+
|
125
|
+
`MAJOR.MINOR.PATCH`
|
126
|
+
|
127
|
+
**1** MAJOR version when you make incompatible API changes
|
128
|
+
|
129
|
+
**2** MINOR version when you add functionality in a backwards-compatible manner, and
|
130
|
+
|
131
|
+
**3** PATCH version when you make backwards-compatible bug fixes.
|
132
|
+
|
133
|
+
#### Internal
|
134
|
+
|
135
|
+
**1** MAJOR version when major changes are made.
|
136
|
+
|
137
|
+
**2** MINOR version when you make incompatible API changes
|
138
|
+
|
139
|
+
**3** PATCH version when you make any backwards-compatible change.
|
140
|
+
|
141
|
+
### Releasing
|
142
|
+
|
143
|
+
#### Prerequisites
|
144
|
+
|
145
|
+
- Gemfury CLI installed
|
146
|
+
- Gemfury Credentials from 1Password
|
147
|
+
|
148
|
+
#### Instructions
|
149
|
+
|
150
|
+
**1.** Update `lib/{gem_name}/version.rb` according to the versioning rules above.
|
151
|
+
|
152
|
+
**2.** Create a Pull Request with the version change to the repository.
|
153
|
+
|
154
|
+
**3.** Once the pull request is merged, run `gem build {gem_name}.gemspec`
|
155
|
+
|
156
|
+
**4.** Run `fury push {gem_name}_{version}.gem`.
|
data/Rakefile
CHANGED
@@ -5,9 +5,25 @@ rescue LoadError
|
|
5
5
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
6
|
end
|
7
7
|
|
8
|
+
# load rspec so we can set up the environment correctly using spec_helper
|
8
9
|
require 'rspec/core/rake_task'
|
10
|
+
require_relative './spec/spec_helper.rb'
|
9
11
|
RSpec::Core::RakeTask.new(:spec)
|
10
12
|
|
11
13
|
Bundler::GemHelper.install_tasks
|
12
14
|
|
13
|
-
|
15
|
+
# load our dummy app so we have access to rails rake tasks such as db:create and db:migrate
|
16
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
17
|
+
begin
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
rescue LoadError
|
20
|
+
# for Rails 3.0.x we need to load the Rakefile directly, as the engine rake task doesn't exist
|
21
|
+
load APP_RAKEFILE
|
22
|
+
end
|
23
|
+
|
24
|
+
task(:default).clear
|
25
|
+
task :default => [
|
26
|
+
'db:create',
|
27
|
+
'db:migrate',
|
28
|
+
'spec'
|
29
|
+
]
|
@@ -5,6 +5,7 @@ module Cassy
|
|
5
5
|
attr_accessor :options
|
6
6
|
attr_reader :username # make this accessible so that we can pick up any
|
7
7
|
# transformations done within the authenticator
|
8
|
+
attr_accessor :client_app_user_field
|
8
9
|
end
|
9
10
|
|
10
11
|
# This is called at server startup.
|
@@ -18,6 +19,9 @@ module Cassy
|
|
18
19
|
raise NotImplementedError
|
19
20
|
end
|
20
21
|
|
22
|
+
def self.find_user_from_ticket(ticket)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
21
25
|
# This is called prior to #validate (i.e. each time the user tries to log in).
|
22
26
|
# Any per-instance initialization for the authenticator should be done here.
|
23
27
|
#
|
@@ -60,8 +64,8 @@ module Cassy
|
|
60
64
|
def self.read_standard_credentials(credentials)
|
61
65
|
@username = credentials[:username]
|
62
66
|
@password = credentials[:password]
|
63
|
-
@service
|
64
|
-
@request
|
67
|
+
@service = credentials[:service]
|
68
|
+
@request = credentials[:request]
|
65
69
|
end
|
66
70
|
|
67
71
|
def extra_attributes_to_extract
|
@@ -1,18 +1,26 @@
|
|
1
1
|
module Cassy
|
2
2
|
module Authenticators
|
3
3
|
class Devise < Base
|
4
|
-
|
4
|
+
|
5
5
|
def self.find_user(credentials)
|
6
6
|
# Find the user with the given email
|
7
7
|
method = "find_by_#{Cassy.config[:username_field] || 'email'}"
|
8
8
|
User.send(method, credentials[:username])
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
|
+
def self.find_user_from_ticket(ticket)
|
12
|
+
return if ticket.nil?
|
13
|
+
key = Cassy.config[:client_app_user_field] || Cassy.config[:username_field] || "email"
|
14
|
+
method = "find_by_#{key}"
|
15
|
+
username = Cassy.config[:concurrent_session_types] ? Cassy.config[:concurrent_session_types].each{ |cst| break ticket.username.rpartition("-#{cst}").first if ticket.username.match("-#{cst}") } : ticket.username
|
16
|
+
User.send(method, username)
|
17
|
+
end
|
18
|
+
|
11
19
|
def self.validate(credentials)
|
12
20
|
user = find_user(credentials)
|
13
|
-
# Did we find a user, and is their password valid?
|
14
|
-
user && user.valid_password?(credentials[:password])
|
21
|
+
# Did we find a user, are they active? and is their password valid?
|
22
|
+
user && user.active_for_authentication? && user.valid_password?(credentials[:password])
|
15
23
|
end
|
16
24
|
end
|
17
25
|
end
|
18
|
-
end
|
26
|
+
end
|
@@ -15,13 +15,20 @@ class Cassy::Authenticators::Test < Cassy::Authenticators::Base
|
|
15
15
|
|
16
16
|
return @password == valid_password
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def self.find_user(*args)
|
20
20
|
# To stop NotImplementedError raising
|
21
21
|
@user = Object.new
|
22
22
|
def @user.full_name
|
23
23
|
"Example User"
|
24
24
|
end
|
25
|
+
def @user.username
|
26
|
+
"Users Username"
|
27
|
+
end
|
25
28
|
@user
|
26
29
|
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
alias_method :find_user_from_ticket, :find_user
|
33
|
+
end
|
27
34
|
end
|
data/lib/cassy/cas.rb
CHANGED
@@ -9,19 +9,6 @@ require 'cassy/utils'
|
|
9
9
|
module Cassy
|
10
10
|
module CAS
|
11
11
|
|
12
|
-
class Error
|
13
|
-
attr_reader :code, :message
|
14
|
-
|
15
|
-
def initialize(code, message)
|
16
|
-
@code = code
|
17
|
-
@message = message
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_s
|
21
|
-
message
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
12
|
def settings
|
26
13
|
Cassy.config
|
27
14
|
end
|
@@ -37,35 +24,15 @@ module Cassy
|
|
37
24
|
lt
|
38
25
|
end
|
39
26
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# validation requests from clients.
|
46
|
-
def generate_ticket_granting_ticket(username, extra_attributes={})
|
47
|
-
# 3.6 (ticket granting cookie/ticket)
|
48
|
-
tgt = Cassy::TicketGrantingTicket.new
|
49
|
-
tgt.ticket = "TGC-" + Cassy::Utils.random_string
|
50
|
-
tgt.username = username
|
51
|
-
tgt.extra_attributes = extra_attributes
|
52
|
-
tgt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
|
53
|
-
tgt.save!
|
54
|
-
tgt
|
27
|
+
def find_or_generate_service_tickets(username, tgt, hostname)
|
28
|
+
@service_tickets={}
|
29
|
+
valid_services.each do |service|
|
30
|
+
@service_tickets[service] = Cassy::ServiceTicket.find_or_generate(service, username, tgt, hostname)
|
31
|
+
end
|
55
32
|
end
|
56
33
|
|
57
|
-
def
|
58
|
-
|
59
|
-
st = ServiceTicket.new
|
60
|
-
st.ticket = "ST-" + Cassy::Utils.random_string
|
61
|
-
st.service = service
|
62
|
-
st.username = username
|
63
|
-
st.granted_by_tgt_id = tgt.id
|
64
|
-
st.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
|
65
|
-
st.save!
|
66
|
-
logger.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" +
|
67
|
-
" for user '#{st.username}' at '#{st.client_hostname}'")
|
68
|
-
st
|
34
|
+
def valid_services
|
35
|
+
@valid_services || settings[:service_list][Rails.env]
|
69
36
|
end
|
70
37
|
|
71
38
|
def generate_proxy_ticket(target_service, pgt)
|
@@ -99,7 +66,7 @@ module Cassy
|
|
99
66
|
https.start do |conn|
|
100
67
|
path = uri.path.empty? ? '/' : uri.path
|
101
68
|
path += '?' + uri.query unless (uri.query.nil? || uri.query.empty?)
|
102
|
-
|
69
|
+
|
103
70
|
pgt = ProxyGrantingTicket.new
|
104
71
|
pgt.ticket = "PGT-" + Cassy::Utils.random_string(60)
|
105
72
|
pgt.iou = "PGTIOU-" + Cassy::Utils.random_string(57)
|
@@ -114,7 +81,7 @@ module Cassy
|
|
114
81
|
response = conn.request_get(path)
|
115
82
|
# TODO: follow redirects... 2.5.4 says that redirects MAY be followed
|
116
83
|
# NOTE: The following response codes are valid according to the JA-SIG implementation even without following redirects
|
117
|
-
|
84
|
+
|
118
85
|
if %w(200 202 301 302 304).include?(response.code)
|
119
86
|
# 3.4 (proxy-granting ticket IOU)
|
120
87
|
pgt.save!
|
@@ -127,162 +94,12 @@ module Cassy
|
|
127
94
|
end
|
128
95
|
end
|
129
96
|
|
130
|
-
def validate_login_ticket(ticket)
|
131
|
-
logger.debug("Validating login ticket '#{ticket}'")
|
132
|
-
|
133
|
-
success = false
|
134
|
-
if ticket.nil?
|
135
|
-
error = "Your login request did not include a login ticket. There may be a problem with the authentication system."
|
136
|
-
logger.warn "Missing login ticket."
|
137
|
-
elsif lt = LoginTicket.find_by_ticket(ticket)
|
138
|
-
if lt.consumed?
|
139
|
-
error = "The login ticket you provided has already been used up. Please try logging in again."
|
140
|
-
logger.warn "Login ticket '#{ticket}' previously used up"
|
141
|
-
elsif Time.now - lt.created_on < settings[:maximum_unused_login_ticket_lifetime]
|
142
|
-
logger.info "Login ticket '#{ticket}' successfully validated"
|
143
|
-
else
|
144
|
-
error = "You took too long to enter your credentials. Please try again."
|
145
|
-
logger.warn "Expired login ticket '#{ticket}'"
|
146
|
-
end
|
147
|
-
else
|
148
|
-
error = "The login ticket you provided is invalid. There may be a problem with the authentication system."
|
149
|
-
logger.warn "Invalid login ticket '#{ticket}'"
|
150
|
-
end
|
151
|
-
|
152
|
-
lt.consume! if lt && !error.blank?
|
153
|
-
|
154
|
-
error
|
155
|
-
end
|
156
|
-
|
157
|
-
def validate_ticket_granting_ticket(ticket)
|
158
|
-
logger.debug("Validating ticket granting ticket '#{ticket}'")
|
159
|
-
|
160
|
-
if ticket.nil?
|
161
|
-
error = "No ticket granting ticket given."
|
162
|
-
logger.debug error
|
163
|
-
elsif tgt = TicketGrantingTicket.find_by_ticket(ticket)
|
164
|
-
if settings[:maximum_session_lifetime] && Time.now - tgt.created_on > settings[:maximum_session_lifetime]
|
165
|
-
tgt.destroy
|
166
|
-
error = "Your session has expired. Please log in again."
|
167
|
-
logger.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired."
|
168
|
-
else
|
169
|
-
logger.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' successfully validated."
|
170
|
-
end
|
171
|
-
else
|
172
|
-
error = "Invalid ticket granting ticket '#{ticket}' (no matching ticket found in the database)."
|
173
|
-
logger.warn(error)
|
174
|
-
end
|
175
|
-
|
176
|
-
[tgt, error]
|
177
|
-
end
|
178
|
-
|
179
|
-
def validate_service_ticket(service, ticket, allow_proxy_tickets = false)
|
180
|
-
logger.debug "Validating service/proxy ticket '#{ticket}' for service '#{service}'"
|
181
|
-
|
182
|
-
if service.nil? or ticket.nil?
|
183
|
-
error = Error.new(:INVALID_REQUEST, "Ticket or service parameter was missing in the request.")
|
184
|
-
logger.warn "#{error.code} - #{error.message}"
|
185
|
-
elsif st = ServiceTicket.find_by_ticket(ticket)
|
186
|
-
if st.consumed?
|
187
|
-
error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has already been used up.")
|
188
|
-
logger.warn "#{error.code} - #{error.message}"
|
189
|
-
elsif st.kind_of?(Cassy::ProxyTicket) && !allow_proxy_tickets
|
190
|
-
error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' is a proxy ticket, but only service tickets are allowed here.")
|
191
|
-
logger.warn "#{error.code} - #{error.message}"
|
192
|
-
elsif Time.now - st.created_on > settings[:maximum_unused_service_ticket_lifetime]
|
193
|
-
error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has expired.")
|
194
|
-
logger.warn "Ticket '#{ticket}' has expired."
|
195
|
-
elsif !st.matches_service? service
|
196
|
-
error = Error.new(:INVALID_SERVICE, "The ticket '#{ticket}' belonging to user '#{st.username}' is valid,"+
|
197
|
-
" but the requested service '#{service}' does not match the service '#{st.service}' associated with this ticket.")
|
198
|
-
logger.warn "#{error.code} - #{error.message}"
|
199
|
-
else
|
200
|
-
logger.info("Ticket '#{ticket}' for service '#{service}' for user '#{st.username}' successfully validated.")
|
201
|
-
end
|
202
|
-
else
|
203
|
-
error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' not recognized.")
|
204
|
-
logger.warn("#{error.code} - #{error.message}")
|
205
|
-
end
|
206
|
-
|
207
|
-
if st
|
208
|
-
st.consume!
|
209
|
-
end
|
210
|
-
|
211
|
-
|
212
|
-
[st, error]
|
213
|
-
end
|
214
|
-
|
215
|
-
def validate_proxy_ticket(service, ticket)
|
216
|
-
pt, error = validate_service_ticket(service, ticket, true)
|
217
|
-
|
218
|
-
if pt.kind_of?(Cassy::ProxyTicket) && !error
|
219
|
-
if not pt.granted_by_pgt
|
220
|
-
error = Error.new(:INTERNAL_ERROR, "Proxy ticket '#{pt}' belonging to user '#{pt.username}' is not associated with a proxy granting ticket.")
|
221
|
-
elsif not pt.granted_by_pgt.service_ticket
|
222
|
-
error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{pt.granted_by_pgt}'"+
|
223
|
-
" (associated with proxy ticket '#{pt}' and belonging to user '#{pt.username}' is not associated with a service ticket.")
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
[pt, error]
|
228
|
-
end
|
229
|
-
|
230
|
-
def validate_proxy_granting_ticket(ticket)
|
231
|
-
if ticket.nil?
|
232
|
-
error = Error.new(:INVALID_REQUEST, "pgt parameter was missing in the request.")
|
233
|
-
logger.warn("#{error.code} - #{error.message}")
|
234
|
-
elsif pgt = ProxyGrantingTicket.find_by_ticket(ticket)
|
235
|
-
if pgt.service_ticket
|
236
|
-
logger.info("Proxy granting ticket '#{ticket}' belonging to user '#{pgt.service_ticket.username}' successfully validated.")
|
237
|
-
else
|
238
|
-
error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{ticket}' is not associated with a service ticket.")
|
239
|
-
logger.error("#{error.code} - #{error.message}")
|
240
|
-
end
|
241
|
-
else
|
242
|
-
error = Error.new(:BAD_PGT, "Invalid proxy granting ticket '#{ticket}' (no matching ticket found in the database).")
|
243
|
-
logger.warn("#{error.code} - #{error.message}")
|
244
|
-
end
|
245
|
-
|
246
|
-
[pgt, error]
|
247
|
-
end
|
248
|
-
|
249
|
-
# Takes an existing ServiceTicket object (presumably pulled from the database)
|
250
|
-
# and sends a POST with logout information to the service that the ticket
|
251
|
-
# was generated for.
|
252
|
-
#
|
253
|
-
# This makes possible the "single sign-out" functionality added in CAS 3.1.
|
254
|
-
# See http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out
|
255
|
-
def send_logout_notification_for_service_ticket(st)
|
256
|
-
uri = URI.parse(st.service)
|
257
|
-
uri.path = '/' if uri.path.empty?
|
258
|
-
time = Time.now
|
259
|
-
rand = Cassy::Utils.random_string
|
260
|
-
|
261
|
-
begin
|
262
|
-
response = Net::HTTP.post_form(uri, {'logoutRequest' => URI.escape(%{<samlp:LogoutRequest ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
|
263
|
-
<saml:NameID></saml:NameID>
|
264
|
-
<samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
|
265
|
-
</samlp:LogoutRequest>})})
|
266
|
-
if response.kind_of? Net::HTTPSuccess
|
267
|
-
logger.info "Logout notification successfully posted to #{st.service.inspect}."
|
268
|
-
return true
|
269
|
-
else
|
270
|
-
logger.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'!"
|
271
|
-
return false
|
272
|
-
end
|
273
|
-
rescue Exception => e
|
274
|
-
logger.error "Failed to send logout notification to service #{st.service.inspect} due to #{e}"
|
275
|
-
return false
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
97
|
def service_uri_with_ticket(service, st)
|
280
98
|
raise ArgumentError, "Second argument must be a ServiceTicket!" unless st.kind_of? Cassy::ServiceTicket
|
281
99
|
|
282
100
|
# This will choke with a URI::InvalidURIError if service URI is not properly URI-escaped...
|
283
101
|
# This exception is handled further upstream (i.e. in the controller).
|
284
102
|
service_uri = URI.parse(service)
|
285
|
-
|
286
103
|
if service.include? "?"
|
287
104
|
if service_uri.query.empty?
|
288
105
|
query_separator = ""
|
@@ -325,5 +142,105 @@ module Cassy
|
|
325
142
|
end
|
326
143
|
module_function :clean_service_url
|
327
144
|
|
145
|
+
def base_service_url(full_service_url)
|
146
|
+
# strips a url back to the domain part only
|
147
|
+
# so that a service ticket can work for all urls on a given domain
|
148
|
+
# eg http://www.something.com/something_else
|
149
|
+
# is stripped back to
|
150
|
+
# http://www.something.com
|
151
|
+
# expects it to be in 'http://x' form
|
152
|
+
return unless full_service_url
|
153
|
+
match = full_service_url.match(/(http(s?):\/\/[a-z0-9\.:]*)/)
|
154
|
+
match && match[0]
|
155
|
+
end
|
156
|
+
module_function :base_service_url
|
157
|
+
|
158
|
+
def detect_ticketing_service(service)
|
159
|
+
# try to find the service in the valid_services list
|
160
|
+
# if loosely_matched_services is true, try to match the base url of the service to one in the valid_services list
|
161
|
+
# if still no luck, check if there is a default_redirect_url that we can use
|
162
|
+
@service||= service
|
163
|
+
@ticketing_service||= valid_services.detect{|s| s == @service } ||
|
164
|
+
(settings[:loosely_match_services] == true && valid_services.detect{|s| base_service_url(s) == base_service_url(@service)})
|
165
|
+
if !@ticketing_service && settings[:default_redirect_url]
|
166
|
+
@default_redirect_url||= settings[:default_redirect_url][Rails.env]
|
167
|
+
@ticketing_service = @default_redirect_url
|
168
|
+
end
|
169
|
+
@username||= params[:username].try(:strip)
|
170
|
+
@password||= params[:password]
|
171
|
+
@lt||= params['lt']
|
172
|
+
end
|
173
|
+
module_function :detect_ticketing_service
|
174
|
+
|
175
|
+
def cas_login(session_type = nil)
|
176
|
+
if valid_credentials?
|
177
|
+
username = ticket_username
|
178
|
+
username = "#{username}-#{session_type}" if Cassy.config[:concurrent_session_types]
|
179
|
+
|
180
|
+
@tgt||= Cassy::TicketGrantingTicket.generate(username, @extra_attributes, @hostname)
|
181
|
+
@existing_ticket_for_service = @tgt.granted_service_tickets.where(:service => @service).where("created_on > ?", Time.now - Cassy.config[:maximum_session_lifetime]).where("consumed IS NOT NULL").first
|
182
|
+
response.set_cookie('tgt', @tgt.to_s)
|
183
|
+
if @ticketing_service
|
184
|
+
find_or_generate_service_tickets(username, @tgt, @hostname)
|
185
|
+
@st = @service_tickets[@ticketing_service]
|
186
|
+
@service_with_ticket = @service && @st ? service_uri_with_ticket(@service, @st) : @default_redirect_url
|
187
|
+
end
|
188
|
+
# if there is an existing ticket for the service that redirected to cassy,
|
189
|
+
# then the session isn't valid on the serviced app and we don't want to redirect to it.
|
190
|
+
# Returning false here will fail the cas_login and the controller will see the instance variable and redirect to logout.
|
191
|
+
!@existing_ticket_for_service
|
192
|
+
else
|
193
|
+
false
|
194
|
+
end
|
195
|
+
end
|
196
|
+
module_function :cas_login
|
197
|
+
|
198
|
+
# Initializes authenticator, returns true / false depending on if user credentials are accurate
|
199
|
+
def valid_credentials?
|
200
|
+
detect_ticketing_service(params[:service])
|
201
|
+
@extra_attributes = {}
|
202
|
+
# Should probably be moved out of the request cycle and into an after init hook on the engine
|
203
|
+
|
204
|
+
credentials = { :username => @username,
|
205
|
+
:password => @password,
|
206
|
+
:service => @service,
|
207
|
+
:request => @env
|
208
|
+
}
|
209
|
+
@user = authenticator.find_user(credentials) || authenticator.find_user_from_ticket(@tgt)
|
210
|
+
valid = ((@user == @ticketed_user) || authenticator.validate(credentials)) && !!@user
|
211
|
+
if valid && @user
|
212
|
+
authenticator.extra_attributes_to_extract.each do |attr|
|
213
|
+
@extra_attributes[attr] = @user.send(attr)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
return valid
|
217
|
+
end
|
218
|
+
module_function :valid_credentials?
|
219
|
+
|
220
|
+
protected
|
221
|
+
|
222
|
+
def ticket_username
|
223
|
+
# Store this into someticket.username
|
224
|
+
# It will get used to find users in client apps
|
225
|
+
user = @user || @ticketed_user
|
226
|
+
@cas_client_username = user.send(settings["client_app_user_field"]) if settings["client_app_user_field"].present? && !!user
|
227
|
+
@cas_client_username || @username
|
228
|
+
end
|
229
|
+
|
230
|
+
def ticketed_user(ticket)
|
231
|
+
# Find the SSO's instance of the user
|
232
|
+
@ticketed_user ||= authenticator.find_user_from_ticket(ticket) unless !ticket
|
233
|
+
end
|
234
|
+
|
235
|
+
def authenticator
|
236
|
+
unless @authenticator
|
237
|
+
auth_settings = Cassy.config["authenticator"]
|
238
|
+
@authenticator ||= auth_settings["class"].constantize
|
239
|
+
@authenticator.configure(auth_settings)
|
240
|
+
end
|
241
|
+
@authenticator
|
242
|
+
end
|
243
|
+
|
328
244
|
end
|
245
|
+
|
329
246
|
end
|
data/lib/cassy/engine.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module Cassy
|
2
2
|
class Engine < Rails::Engine
|
3
|
-
|
4
|
-
config.
|
5
|
-
|
6
|
-
config.after_initialize do
|
7
|
-
config.configuration = HashWithIndifferentAccess.new(YAML.load_file(config.config_file))
|
3
|
+
config.config_file = ENV["RUBYCAS_CONFIG_FILE"]
|
4
|
+
config.after_initialize do
|
5
|
+
config.configuration = HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(config.config_file)).result))
|
8
6
|
end
|
9
7
|
end
|
10
8
|
end
|
data/lib/cassy/routes.rb
CHANGED
@@ -4,15 +4,15 @@ module ActionDispatch::Routing
|
|
4
4
|
options[:controllers] ||= HashWithIndifferentAccess.new
|
5
5
|
options[:controllers][:sessions] ||= "cassy/sessions"
|
6
6
|
scope(:path => "cas") do
|
7
|
-
|
7
|
+
mount Cassy::API => '/api'
|
8
8
|
get 'login', :to => "#{options[:controllers][:sessions]}#new"
|
9
9
|
post 'login', :to => "#{options[:controllers][:sessions]}#create"
|
10
|
-
|
10
|
+
|
11
11
|
get 'logout', :to => "#{options[:controllers][:sessions]}#destroy"
|
12
|
-
|
12
|
+
|
13
13
|
get 'serviceValidate', :to => "#{options[:controllers][:sessions]}#service_validate"
|
14
14
|
get 'proxyValidate', :to => "#{options[:controllers][:sessions]}#proxy_validate"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
|
-
end
|
18
|
+
end
|
data/lib/cassy/utils.rb
CHANGED
metadata
CHANGED
@@ -1,161 +1,214 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassy
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 1.1.4
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.2.0
|
6
5
|
platform: ruby
|
7
|
-
authors:
|
8
|
-
- ryan
|
6
|
+
authors:
|
7
|
+
- ryan bigg
|
8
|
+
- geoff@reinteractive.net
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2017-06-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
16
15
|
name: crypt-isaac
|
17
|
-
requirement:
|
18
|
-
|
19
|
-
requirements:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
20
18
|
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version:
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.0.0
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
|
-
version_requirements:
|
26
|
-
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.0.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
27
29
|
name: rails
|
28
|
-
requirement:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 3.0.9
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 3.0.9
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: grape
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.14'
|
34
49
|
type: :runtime
|
35
50
|
prerelease: false
|
36
|
-
version_requirements:
|
37
|
-
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.14'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: grape-entity
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.5'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.5'
|
70
|
+
- !ruby/object:Gem::Dependency
|
38
71
|
name: rspec-rails
|
39
|
-
requirement:
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
version: 2.6.0
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.7.0
|
45
77
|
type: :development
|
46
78
|
prerelease: false
|
47
|
-
version_requirements:
|
48
|
-
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.7.0
|
84
|
+
- !ruby/object:Gem::Dependency
|
49
85
|
name: capybara
|
50
|
-
requirement:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
version: "1.0"
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.0'
|
56
91
|
type: :development
|
57
92
|
prerelease: false
|
58
|
-
version_requirements:
|
59
|
-
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
60
99
|
name: database_cleaner
|
61
|
-
requirement:
|
62
|
-
|
63
|
-
requirements:
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
64
102
|
- - ">="
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version:
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
67
105
|
type: :development
|
68
106
|
prerelease: false
|
69
|
-
version_requirements:
|
70
|
-
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
71
113
|
name: sqlite3
|
72
|
-
requirement:
|
73
|
-
|
74
|
-
requirements:
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
75
116
|
- - ">="
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version:
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
78
119
|
type: :development
|
79
120
|
prerelease: false
|
80
|
-
version_requirements:
|
81
|
-
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
82
127
|
name: launchy
|
83
|
-
requirement:
|
84
|
-
|
85
|
-
requirements:
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
86
130
|
- - ">="
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version:
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
89
133
|
type: :development
|
90
134
|
prerelease: false
|
91
|
-
version_requirements:
|
92
|
-
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
93
141
|
name: devise
|
94
|
-
requirement:
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - "~>"
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 3.2.4
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - "~>"
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: 3.2.4
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: webmock
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
100
161
|
type: :development
|
101
162
|
prerelease: false
|
102
|
-
version_requirements:
|
103
|
-
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
description: An engine that provides a CAS server to the application it's included
|
169
|
+
within.
|
104
170
|
email:
|
105
171
|
executables: []
|
106
|
-
|
107
172
|
extensions: []
|
108
|
-
|
109
173
|
extra_rdoc_files: []
|
110
|
-
|
111
|
-
|
174
|
+
files:
|
175
|
+
- MIT-LICENSE
|
176
|
+
- README.md
|
177
|
+
- Rakefile
|
178
|
+
- lib/cassy.rb
|
179
|
+
- lib/cassy/authenticators.rb
|
112
180
|
- lib/cassy/authenticators/base.rb
|
113
181
|
- lib/cassy/authenticators/devise.rb
|
114
182
|
- lib/cassy/authenticators/test.rb
|
115
|
-
- lib/cassy/authenticators.rb
|
116
183
|
- lib/cassy/cas.rb
|
117
184
|
- lib/cassy/engine.rb
|
118
185
|
- lib/cassy/generators/views_generator.rb
|
119
186
|
- lib/cassy/models.rb
|
120
187
|
- lib/cassy/routes.rb
|
121
188
|
- lib/cassy/utils.rb
|
122
|
-
- lib/cassy.rb
|
189
|
+
- lib/cassy/version.rb
|
123
190
|
- lib/tasks/cassy_tasks.rake
|
124
|
-
- MIT-LICENSE
|
125
|
-
- Rakefile
|
126
|
-
- README.markdown
|
127
191
|
homepage:
|
128
192
|
licenses: []
|
129
|
-
|
193
|
+
metadata: {}
|
130
194
|
post_install_message:
|
131
195
|
rdoc_options: []
|
132
|
-
|
133
|
-
require_paths:
|
196
|
+
require_paths:
|
134
197
|
- lib
|
135
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
-
|
137
|
-
requirements:
|
198
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
138
200
|
- - ">="
|
139
|
-
- !ruby/object:Gem::Version
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
version: "0"
|
144
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
-
none: false
|
146
|
-
requirements:
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
147
205
|
- - ">="
|
148
|
-
- !ruby/object:Gem::Version
|
149
|
-
|
150
|
-
segments:
|
151
|
-
- 0
|
152
|
-
version: "0"
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
153
208
|
requirements: []
|
154
|
-
|
155
209
|
rubyforge_project:
|
156
|
-
rubygems_version:
|
210
|
+
rubygems_version: 2.6.4
|
157
211
|
signing_key:
|
158
|
-
specification_version:
|
159
|
-
summary:
|
212
|
+
specification_version: 4
|
213
|
+
summary: Cassy is a rails CAS engine
|
160
214
|
test_files: []
|
161
|
-
|
data/README.markdown
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
# Cassy
|
2
|
-
|
3
|
-
This project is designed to be a Rails 3.0 engine that uses a large portion of the code from the [rubycas-server][https://github.com/gunark/rubycas-server] project. Certain portions of this code belong to the rubycas-server project owners.
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
This engine currently only works with Rails 3.0. To have it work with the application you must do three things:
|
8
|
-
|
9
|
-
**Install as a gem**
|
10
|
-
|
11
|
-
Put this line in your project's `Gemfile`:
|
12
|
-
|
13
|
-
gem 'cassy'
|
14
|
-
|
15
|
-
Create a new initializer (probably called `config/initializers/cassy.rb`) and point cassy at the correct configuration file of your application:
|
16
|
-
|
17
|
-
Cassy::Engine.config.config_file = Rails.root + "config/cassy.yml"
|
18
|
-
|
19
|
-
Create this configuration file at `config/cassy.yml`. Fill it with these values:
|
20
|
-
|
21
|
-
# Times are in seconds.
|
22
|
-
maximum_unused_login_ticket_lifetime: 300
|
23
|
-
maximum_unused_service_ticket_lifetime: 300
|
24
|
-
|
25
|
-
authenticator:
|
26
|
-
class: Cassy::Authenticators::Devise
|
27
|
-
|
28
|
-
The first two keys are the time-to-expiry for the login and service tickets respectively. The class for the authentication can be any constant which responds to a `validates` method. By default, only Devise authentication is supported at the moment.
|
29
|
-
|
30
|
-
Next, you will need to tell Cassy to load its routes in your application which you can do by calling `cassy` in `config/routes.rb`:
|
31
|
-
|
32
|
-
Rails.application.routes.draw do
|
33
|
-
cassy
|
34
|
-
|
35
|
-
# your routes go here
|
36
|
-
end
|
37
|
-
|
38
|
-
Boom, done. Now this application will act as a CAS server.
|
39
|
-
|
40
|
-
For customization options please see the "Customization" section below.
|
41
|
-
|
42
|
-
## Configuration
|
43
|
-
|
44
|
-
The configuration options for this gem goes into a file called `config/cassy.yml` at the root of the project if you've set it up as advised, and this allows the engine to be configured.
|
45
|
-
|
46
|
-
These configuration options are detailed here for your convenience. For specific term definitions, please consult the CAS spec.
|
47
|
-
|
48
|
-
`authenticator`: Must specify at least one key, `class`, which is a string version of a constant that will be used for authentication in the system. This constant *must* respond to `validate`.
|
49
|
-
`maximum_unused_login_ticket_lifetime`: The time before a login ticket would expire.
|
50
|
-
`maximum_unused_service_ticket_lifetime`: The time before a service ticket would expire.
|
51
|
-
`username_field`: Defines the field on the users table which is used for the lookup for the username. Defaults to "username".
|
52
|
-
`username_label`: Allows for the "Username" label on the sign in page to be given a different value. Helpful if you want to call it "Email" or "User Name" instead.
|
53
|
-
|
54
|
-
## Customization
|
55
|
-
|
56
|
-
### Sessions Controller
|
57
|
-
|
58
|
-
In Cassy, it is possible to override the controller which is used for authentication. To do this, the controller can be configured in `config/routes.rb`:
|
59
|
-
|
60
|
-
cassy :controllers => "sessions"
|
61
|
-
|
62
|
-
By doing this, it will point at the `SessionsController` rather than the default of `Cassy::SessionsController`. This controller then should inherit from `Cassy::SessionsController` to inherit the original behaviour and will need to point to the views of Cassy:
|
63
|
-
|
64
|
-
class SessionsController < Cassy::SessionsController
|
65
|
-
def new
|
66
|
-
# custom behaviour goes here
|
67
|
-
super
|
68
|
-
end
|
69
|
-
|
70
|
-
|