Class: Rack::CASClient

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/cas_client.rb

Overview

Middleware component to authenticate with a CAS server.

Constant Summary collapse

VERSION =
'0.3.0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app = nil, opts = {}) ⇒ CASClient

options - Hash

:logger - Logger, must respond to :<<
Everything else is used by CASClient::Client#configure. Notable params are:
:cas_base_url - String, your CAS Server base url. raises if not there.
:force_ssl_verification - Bool, force ssl verification? Defaults to true.

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
# File 'lib/rack/cas_client.rb', line 29

def initialize app = nil, opts = {}
  @app = app

  # quiet cas_client
  @logger = opts.delete :logger
  raise ArgumentError, "invalid logger #{@logger}" if
    @logger and not @logger.respond_to? :<<

  opts[:force_ssl_verification] = opts.fetch(:force_ssl_verification, true)
  @cas_client = ::CASClient::Client.new opts
end

Instance Attribute Details

#cas_clientObject (readonly)

Returns the value of attribute cas_client.



22
23
24
# File 'lib/rack/cas_client.rb', line 22

def cas_client
  @cas_client
end

#requestObject (readonly)

Returns the value of attribute request.



22
23
24
# File 'lib/rack/cas_client.rb', line 22

def request
  @request
end

Class Method Details

.require_casclient_depsObject

Public: CASCLient depends on these things but does not explicitely require them. Call this method (before any forking) if you care. (You may not if you implement your own #blank? for example.)



13
14
15
16
17
18
19
20
# File 'lib/rack/cas_client.rb', line 13

def self.require_casclient_deps
  # YUCK! These are required for casclient
  require 'active_support/core_ext/object/blank'
  # I believe one or the other of these is required (not both) but since
  # they're stdlib I'm leaving them in.
  require 'json'
  require 'yaml'
end

Instance Method Details

#appObject

Private: Accessor method for app.

If app is nil, redirects back to the referrer, otherwise, just displays a simple page saying that login was successful.



45
46
47
48
49
50
51
52
53
54
# File 'lib/rack/cas_client.rb', line 45

def app
  @app || lambda{|env|
    req = Rack::Request.new env
    if url = req.params['url']
      self.redirect url
    else
      [200, {'Content-Type'=>'text/plain'},['logged in!']]
    end
  }
end

#authenticated?Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/rack/cas_client.rb', line 115

def authenticated?
  !user.empty? && log("#{user.inspect} is already authenticated")
end

#call(env) ⇒ Object

Public: Rack interface.

Calls app if already authenticated.

If not authenticated, redirects user to login server. Login server should redirect back with a ticket param. If ticket is valid, redirect back to original request.

It’s up to the session manager to manage timeouts etc.



65
66
67
# File 'lib/rack/cas_client.rb', line 65

def call env
  dup.call! env
end

#call!(env) ⇒ Object

Private: Rack call method.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rack/cas_client.rb', line 70

def call! env
  @request = Rack::Request.new env

  return app.call(env) if authenticated?

  service_url = request_url_without_ticket
   = cas_client.(service_url)

  if st = service_ticket(service_url)

    cas_client.validate_service_ticket(st) unless st.has_been_validated?

    if st.is_valid?
      log 'ticket is valid'
      # use string because extra_attributes will always have strings as keys
      user['username'] = st.user
      user.merge!        st.extra_attributes || {}
      log "user logged in as #{user.inspect}"
      redirect service_url
    else
      log 'ticket is not valid'
      user.clear # why?
      redirect 
    end
  else
    log 'No ticket, redirecting to login server'
    redirect 
  end
end

#log(msg) ⇒ Object



119
120
121
122
123
124
125
# File 'lib/rack/cas_client.rb', line 119

def log msg
  return true unless @logger

  @logger << msg
  @logger << "\n" unless msg["\n"]
  true
end

#redirect(loc) ⇒ Object



127
128
129
130
131
132
133
# File 'lib/rack/cas_client.rb', line 127

def redirect loc
  [302,
    { 'Content-Type' => 'text/plain',
      'Content-Length' => '0',
      'Location' => loc},
    ['']]
end

#request_url_without_ticketObject

return the request.url minus params



136
137
138
139
140
141
142
143
144
# File 'lib/rack/cas_client.rb', line 136

def request_url_without_ticket
  # I feel like this should be in Rack::Request
  # request['ticket'] = nil
  # request.url
  url = request.base_url + request.path
  query = request.params.dup
  query.delete 'ticket'
  query.empty? ? url : "#{url}?#{Rack::Utils.build_nested_query query}"
end

#service_ticket(service_url) ⇒ Object

Private: Returns a ticket configured with service_url

service_url - String, the request url that needs to be validated.



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/rack/cas_client.rb', line 103

def service_ticket service_url
  ticket = request.params['ticket']
  return unless ticket
  ticket_class = ticket =~ /^PT-/ ?
                   ::CASClient::ProxyTicket :
                   ::CASClient::ServiceTicket

  st = ticket_class.new(ticket, service_url, false)
  log "User has a #{ticket_class}! #{st.inspect}"
  st
end

#sessionObject



146
147
148
# File 'lib/rack/cas_client.rb', line 146

def session
  request.session
end

#userObject



150
151
152
153
# File 'lib/rack/cas_client.rb', line 150

def user
  # session[:cas_user]
  session[cas_client.username_session_key] ||= {}
end