Class: CASClient::Frameworks::Rails::Filter

Inherits:
Object
  • Object
show all
Defined in:
lib/casclient/frameworks/rails/filter.rb

Direct Known Subclasses

GatewayFilter

Constant Summary collapse

@@config =

These are initialized when you call configure.

nil
@@client =
nil
@@log =
nil
@@fake_user =
nil

Class Method Summary collapse

Class Method Details

.configure(config) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/casclient/frameworks/rails/filter.rb', line 141

def configure(config)
  @@cas_filtered = false
  @@config = config
  @@config[:logger] = RAILS_DEFAULT_LOGGER unless @@config[:logger]
  @@client = CASClient::Client.new(config)
  @@log = client.log
end

.fake(username) ⇒ Object

used to allow faking for testing with cucumber and other tools. use like

CASClient::Frameworks::Rails::Filter.fake("homer")


153
154
155
# File 'lib/casclient/frameworks/rails/filter.rb', line 153

def fake(username)
  @@fake_user = username
end

.filter(controller) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/casclient/frameworks/rails/filter.rb', line 15

def filter(controller)
  raise "Cannot use the CASClient filter because it has not yet been configured." if config.nil?

  if @@cas_filtered
    raise "Filtering more than once might lead to redirection loops ! Don't do it (called from #{caller}, previously from #{@@cas_filtered})"
  else
    @@cas_filtered = caller
  end

  if @@fake_user
    controller.session[client.username_session_key] = @@fake_user
    controller.session[:casfilteruser] = @@fake_user
    return true
  end


  last_st = controller.session[:cas_last_valid_ticket]

  if single_sign_out(controller)
    controller.send(:render, :text => "CAS Single-Sign-Out request intercepted.")
    return false
  end

  st = read_ticket(controller)

  is_new_session = true

  if st && last_st &&
      last_st.ticket == st.ticket &&
      last_st.service == st.service
    # warn() rather than info() because we really shouldn't be re-validating the same ticket.
    # The only situation where this is acceptable is if the user manually does a refresh and
    # the same ticket happens to be in the URL.
    log.warn("Re-using previously validated ticket since the ticket id and service are the same.")
    st = last_st
    is_new_session = false
  elsif last_st &&
      !config[:authenticate_on_every_request] &&
      controller.session[client.username_session_key]
    # Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
    # previously authenticated for this service). This is to prevent redirection to the CAS server on every
    # request.
    #
    # This behaviour can be disabled (so that every request is routed through the CAS server) by setting
    # the :authenticate_on_every_request config option to true. However, this is not desirable since
    # it will almost certainly break POST request, AJAX calls, etc.
    log.debug "Existing local CAS session detected for #{controller.session[client.username_session_key].inspect}. "+
      "Previous ticket #{last_st.ticket.inspect} will be re-used."
    st = last_st
    is_new_session = false
  end

  if st
    client.validate_service_ticket(st) unless st.has_been_validated?
    vr = st.response

    if st.is_valid?
      if is_new_session
        log.info("Ticket #{st.ticket.inspect} for service #{st.service.inspect} belonging to user #{vr.user.inspect} is VALID.")
        controller.session[client.username_session_key] = vr.user.dup
        controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(vr.extra_attributes)

        if vr.extra_attributes
          log.debug("Extra user attributes provided along with ticket #{st.ticket.inspect}: #{vr.extra_attributes.inspect}.")
        end

        # RubyCAS-Client 1.x used :casfilteruser as it's username session key,
        # so we need to set this here to ensure compatibility with configurations
        # built around the old client.
        controller.session[:casfilteruser] = vr.user

        if config[:enable_single_sign_out]
          f = store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
          log.debug("Wrote service session lookup file to #{f.inspect} with session id #{controller.request.session_options[:id] || controller.session.session_id.inspect}.")
        end
      end

      # Store the ticket in the session to avoid re-validating the same service
      # ticket with the CAS server.
      controller.session[:cas_last_valid_ticket] = st

      if vr.pgt_iou
        unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_pgt].iou == vr.pgt_iou
          log.info("Receipt has a proxy-granting ticket IOU. Attempting to retrieve the proxy-granting ticket...")
          pgt = client.retrieve_proxy_granting_ticket(vr.pgt_iou)

          if pgt
            log.debug("Got PGT #{pgt.ticket.inspect} for PGT IOU #{pgt.iou.inspect}. This will be stored in the session.")
            controller.session[:cas_pgt] = pgt
            # For backwards compatibility with RubyCAS-Client 1.x configurations...
            controller.session[:casfilterpgt] = pgt
          else
            log.error("Failed to retrieve a PGT for PGT IOU #{vr.pgt_iou}!")
          end
        else
          log.info("PGT is present in session and PGT IOU #{vr.pgt_iou} matches the saved PGT IOU.  Not retrieving new PGT.")
        end

      end

      return true
    else
      log.warn("Ticket #{st.ticket.inspect} failed validation -- #{vr.failure_code}: #{vr.failure_message}")
      unauthorized!(controller, vr)
      return false
    end
  else # no service ticket was present in the request
    if returning_from_gateway?(controller)
      log.info "Returning from CAS gateway without authentication."

      # unset, to allow for the next request to be authenticated if necessary
      controller.session[:cas_sent_to_gateway] = false

      if use_gatewaying?
        log.info "This CAS client is configured to use gatewaying, so we will permit the user to continue without authentication."
        return true
      else
        log.warn "The CAS client is NOT configured to allow gatewaying, yet this request was gatewayed. Something is not right!"
      end
    end

    unauthorized!(controller)
    return false
  end
end

.login_url(controller) ⇒ Object

Returns the login URL for the current controller. Useful when you want to provide a “Login” link in a GatewayFilter’ed action.



164
165
166
167
168
169
# File 'lib/casclient/frameworks/rails/filter.rb', line 164

def (controller)
  service_url = read_service_url(controller)
  url = client.(service_url)
  log.debug("Generated login url: #{url}")
  return url
end

.logout(controller, service = nil) ⇒ Object

Clears the given controller’s local Rails session, does some local CAS cleanup, and redirects to the CAS logout page. Additionally, the request.referer value from the controller instance is passed to the CAS server as a ‘destination’ parameter. This allows RubyCAS server to provide a follow-up login page allowing the user to log back in to the service they just logged out from using a different username and password. Other CAS server implemenations may use this ‘destination’ parameter in different ways. If given, the optional service URL overrides request.referer.



182
183
184
185
186
187
188
# File 'lib/casclient/frameworks/rails/filter.rb', line 182

def logout(controller, service = nil)
  referer = service || controller.request.referer
  st = controller.session[:cas_last_valid_ticket]
  delete_service_session_lookup(st) if st
  controller.send(:reset_session)
  controller.send(:redirect_to, client.logout_url(referer))
end

.redirect_to_cas_for_authentication(controller) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/casclient/frameworks/rails/filter.rb', line 202

def redirect_to_cas_for_authentication(controller)
  redirect_url = (controller)

  if use_gatewaying?
    controller.session[:cas_sent_to_gateway] = true
    redirect_url << "&gateway=true"
  else
    controller.session[:cas_sent_to_gateway] = false
  end

  if controller.session[:previous_redirect_to_cas] &&
      controller.session[:previous_redirect_to_cas] > (Time.now - 1.second)
    log.warn("Previous redirect to the CAS server was less than a second ago. The client at #{controller.request.remote_ip.inspect} may be stuck in a redirection loop!")
    controller.session[:cas_validation_retry_count] ||= 0

    if controller.session[:cas_validation_retry_count] > 3
      log.error("Redirection loop intercepted. Client at #{controller.request.remote_ip.inspect} will be redirected back to login page and forced to renew authentication.")
      redirect_url += "&renew=1&redirection_loop_intercepted=1"
    end

    controller.session[:cas_validation_retry_count] += 1
  else
    controller.session[:cas_validation_retry_count] = 0
  end
  controller.session[:previous_redirect_to_cas] = Time.now

  log.debug("Redirecting to #{redirect_url.inspect}")
  controller.send(:redirect_to, redirect_url)
end

.unauthorized!(controller, vr = nil) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/casclient/frameworks/rails/filter.rb', line 190

def unauthorized!(controller, vr = nil)
  if controller.params[:format] == "xml"
    if vr
      controller.send(:render, :xml => "<errors><error>#{vr.failure_message}</error></errors>", :status => 401)
    else
      controller.send(:head, 401)
    end
  else
    redirect_to_cas_for_authentication(controller)
  end
end

.use_gatewaying?Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/casclient/frameworks/rails/filter.rb', line 157

def use_gatewaying?
  @@config[:use_gatewaying]
end