-
Notifications
You must be signed in to change notification settings - Fork 216
Expand file tree
/
Copy pathstorage.rb
More file actions
179 lines (148 loc) · 6.82 KB
/
storage.rb
File metadata and controls
179 lines (148 loc) · 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
module CASClient
module Tickets
module Storage
class AbstractTicketStore
attr_accessor :log
def log
@log ||= CASClient::LoggerWrapper.new
end
def process_single_sign_out(st)
session_id, session = get_session_for_service_ticket(st)
if session
session.destroy
log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{st.inspect}.")
else
log.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.")
end
if session_id
log.info("Single-sign-out for service ticket #{session_id.inspect} completed successfuly.")
else
log.debug("No session id found for CAS ticket #{st}")
end
end
def get_session_for_service_ticket(st)
session_id = read_service_session_lookup(st)
unless session_id.nil?
# This feels a bit hackish, but there isn't really a better way to go about it that I am aware of yet
if ActiveRecord::SessionStore.respond_to?(:session_class)
session = ActiveRecord::SessionStore.session_class.find(:first, :conditions => {:session_id => session_id})
else
session = ActionDispatch::Session::ActiveRecordStore.session_class.find_by(:session_id => session_id)
end
else
log.warn("Couldn't destroy session service ticket #{st} because no corresponding session id could be found.")
end
[session_id, session]
end
def store_service_session_lookup(st, controller)
raise 'Implement this in a subclass!'
end
def cleanup_service_session_lookup(st)
raise 'Implement this in a subclass!'
end
def save_pgt_iou(pgt_iou, pgt)
raise 'Implement this in a subclass!'
end
def retrieve_pgt(pgt_iou)
raise 'Implement this in a subclass!'
end
protected
def read_service_session_lookup(st)
raise 'Implement this in a subclass!'
end
def session_id_from_controller(controller)
session_id = controller.request.session_options[:id] || controller.session.session_id
raise CASClient::CASException, "Failed to extract session_id from controller" if session_id.nil?
session_id
end
end
# A Ticket Store that keeps it's tickets in a directory on the local filesystem.
# Service tickets are stored under tmp/sessions by default
# and Proxy Granting Tickets and their IOUs are stored in tmp/cas_pgt.pstore
# This Ticket Store works fine for small sites but will most likely have
# concurrency problems under heavy load. It also requires that all your
# worker processes have access to a shared file system.
#
# This ticket store takes the following config parameters
# :storage_dir - The directory to store data in. Defaults to Rails.root/tmp
# :service_session_lookup_dir - The directory to store Service Ticket/Session ID files in. Defaults to :storage_dir/sessions
# :pgt_store_path - The location to store the pgt PStore file. Defaults to :storage_dir/cas_pgt.pstore
class LocalDirTicketStore < AbstractTicketStore
require 'pstore'
def initialize(config={})
config ||= {}
default_tmp_dir = defined?(Rails.root) ? "#{Rails.root}/tmp" : "#{Dir.pwd}/tmp"
@tmp_dir = config[:storage_dir] || default_tmp_dir
@service_session_lookup_dir = config[:service_session_lookup_dir] || "#{@tmp_dir}/sessions"
@pgt_store_path = config[:pgt_store_path] || "#{@tmp_dir}/cas_pgt.pstore"
end
# Creates a file in tmp/sessions linking a SessionTicket
# with the local Rails session id. The file is named
# cas_sess.<session ticket> and its text contents is the corresponding
# Rails session id.
# Returns the filename of the lookup file created.
def store_service_session_lookup(st, controller)
raise CASException, "No service_ticket specified." if st.nil?
raise CASException, "No controller specified." if controller.nil?
sid = session_id_from_controller(controller)
st = st.ticket if st.kind_of? ServiceTicket
f = File.new(filename_of_service_session_lookup(st), 'w')
f.write(sid)
f.close
return f.path
end
# Returns the local Rails session ID corresponding to the given
# ServiceTicket. This is done by reading the contents of the
# cas_sess.<session ticket> file created in a prior call to
# #store_service_session_lookup.
def read_service_session_lookup(st)
raise CASException, "No service_ticket specified." if st.nil?
st = st.ticket if st.kind_of? ServiceTicket
ssl_filename = filename_of_service_session_lookup(st)
return IO.read(ssl_filename) if File.exists?(ssl_filename)
end
# Removes a stored relationship between a ServiceTicket and a local
# Rails session id. This should be called when the session is being
# closed.
#
# See #store_service_session_lookup.
def cleanup_service_session_lookup(st)
raise CASException, "No service_ticket specified." if st.nil?
st = st.ticket if st.kind_of? ServiceTicket
ssl_filename = filename_of_service_session_lookup(st)
File.delete(ssl_filename) if File.exists?(ssl_filename)
end
def save_pgt_iou(pgt_iou, pgt)
raise CASException, "Invalid pgt_iou" if pgt_iou.nil?
raise CASException, "Invalid pgt" if pgt.nil?
# TODO: pstore contents should probably be encrypted...
pstore = open_pstore
pstore.transaction do
pstore[pgt_iou] = pgt
end
end
def retrieve_pgt(pgt_iou)
raise CASException, "No pgt_iou specified. Cannot retrieve the pgt." unless pgt_iou
pstore = open_pstore
pgt = nil
# TODO: need to periodically clean the storage, otherwise it will just keep growing
pstore.transaction do
pgt = pstore[pgt_iou]
pstore.delete pgt_iou
end
raise CASException, "Invalid pgt_iou specified. Perhaps this pgt has already been retrieved?" unless pgt
pgt
end
private
# Returns the path and filename of the service session lookup file.
def filename_of_service_session_lookup(st)
st = st.ticket if st.kind_of? ServiceTicket
return "#{@service_session_lookup_dir}/cas_sess.#{st}"
end
def open_pstore
PStore.new(@pgt_store_path)
end
end
end
end
end