authpds 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012 YOURNAME
1
+ Copyright 2012 Scot Dalton
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -1,14 +1,17 @@
1
1
  = Authpds
2
+ {<img src="https://secure.travis-ci.org/scotdalton/authpds.png?branch=master" alt="Build Status" />}[https://travis-ci.org/scotdalton/authpds]
3
+ {<img src="https://gemnasium.com/scotdalton/authpds.png" alt="Dependency Status" />}[https://gemnasium.com/scotdalton/authpds]
4
+ {<img src="https://codeclimate.com/badge.png" alt="Code Climage" />}[https://codeclimate.com/github/scotdalton/authpds]
2
5
 
3
6
  This gem provides a mechanism for authenticating via Ex Libris' Patron Directory Services (PDS) and provides hooks for making authorization decisions based on the user information provided by PDS. It leverages the authlogic gem and depends on a User-like model.
4
7
 
5
8
  == Basics
6
9
  === Generate User-like model
7
10
  $ rails generate model User username:string email:string firstname:string \
8
- lastname:string mobile_phone:string crypted_password:string password_salt:string \
9
- session_id:string persistence_token:string login_count:integer last_request_at:string \
10
- current_login_at:string last_login_at:string last_login_ip:string current_login_ip:string \
11
- user_attributes:text refreshed_at:datetime
11
+ lastname:string mobile_phone:string crypted_password:string password_salt:string \
12
+ session_id:string persistence_token:string login_count:integer last_request_at:string \
13
+ current_login_at:string last_login_at:string last_login_ip:string current_login_ip:string \
14
+ user_attributes:text refreshed_at:datetime
12
15
 
13
16
  === Configure User-like model
14
17
  class User < ActiveRecord::Base
@@ -72,11 +75,27 @@ You can create multiple institution listings as follows:
72
75
 
73
76
  The two separate institutions above share a link_code in this example. The link_code attribute determines the institute parameter in the PDS url.
74
77
 
78
+ ==== Institution fields
79
+ :name:: Institution name
80
+ :display_name:: Name to display to users.
81
+ :default:: Boolean indicating whether this is a default Institution. Alternatively, an Institution can be named 'default'.
82
+ :parent_institution:: A parent Institution from which this child will inherit fields.
83
+ :ip_addresses:: IP addresses associated with the Institution.
84
+ :login:: Login configurations associated with the Institution.
85
+ :layouts:: Layout configurations associated with the Institution.
86
+ :views:: View configurations associated with the Institution.
87
+ :controllers:: Controller configurations associated with the Institution.
88
+ :models:: Model configurations associated with the Institution.
89
+
90
+
75
91
  ==== Create institution initializer
76
92
 
77
- If you have your institutions.yml file in another location make sure to change the line below accordingly.
93
+ Most likely it will just work if you put the config file in
94
+ "#{Rails.root}/config/institutions.yml"
78
95
 
79
- InstitutionList.yaml_path= Rails.root + "config/institutions.yml"
96
+ If you have your institutions config file in another location make sure to change the line below accordingly.
97
+ Institutions.loadpaths << File.join("other", "path")
98
+ Institutions.filenames << "other_file.yml"
80
99
 
81
100
  === Mixin authpds methods into UserSessionsController
82
101
  class UserSessionsController < ApplicationController
@@ -126,4 +145,6 @@ method, e.g. :before_persisting, :persist, :after_persisting. We're using the :
126
145
  === Access to the controller in Session
127
146
  The class that Session extends, Authologic::Session::Base, has an explicit handle to the current controller via the instance method
128
147
  :controller. This gives our custom instance methods access to cookies, session information, loggers, etc. and also allows them to
129
- perform redirects and renders.
148
+ perform redirects and renders.
149
+
150
+ == Build Status {<img src="https://secure.travis-ci.org/scotdalton/authpds.png"/>}[http://travis-ci.org/scotdalton/authpds]
data/Rakefile CHANGED
@@ -20,13 +20,9 @@ RDoc::Task.new(:rdoc) do |rdoc|
20
20
  rdoc.rdoc_files.include('lib/**/*.rb')
21
21
  end
22
22
 
23
-
24
-
25
-
26
23
  Bundler::GemHelper.install_tasks
27
24
 
28
25
  require 'rake/testtask'
29
-
30
26
  Rake::TestTask.new(:test) do |t|
31
27
  t.libs << 'lib'
32
28
  t.libs << 'test'
@@ -34,5 +30,4 @@ Rake::TestTask.new(:test) do |t|
34
30
  t.verbose = false
35
31
  end
36
32
 
37
-
38
- task :default => :test
33
+ task :default => :test
data/lib/authpds.rb CHANGED
@@ -4,8 +4,6 @@ AUTHPDS_PATH = File.dirname(__FILE__) + "/authpds/"
4
4
  [
5
5
  'acts_as_authentic',
6
6
  'session',
7
- 'institution',
8
- 'institution_list',
9
7
  'exlibris/pds',
10
8
  'controllers/authpds_controller',
11
9
  'controllers/authpds_sessions_controller'
@@ -2,6 +2,7 @@ module Authpds
2
2
  module ActsAsAuthentic
3
3
  def self.included(klass)
4
4
  klass.class_eval do
5
+ require 'institutions'
5
6
  add_acts_as_authentic_module(InstanceMethods, :prepend)
6
7
  end
7
8
  end
@@ -14,7 +15,6 @@ module Authpds
14
15
  end
15
16
  end
16
17
 
17
- public
18
18
  # Setting the username field also resets the persistence_token if the value changes.
19
19
  def username=(value)
20
20
  write_attribute(:username, value)
@@ -22,29 +22,26 @@ module Authpds
22
22
  end
23
23
 
24
24
  def primary_institution
25
- return nil unless InstitutionList.institutions_defined?
26
- InstitutionList.instance.get(user_attributes[:primary_institution]) unless user_attributes.nil?
25
+ all_institutions[user_attributes[:primary_institution]] unless user_attributes.nil?
27
26
  end
28
27
 
29
- def primary_institution=(primary_institution)
30
- primary_institution = primary_institution.name if primary_institution.is_a?(Institution)
31
- self.user_attributes=({:primary_institution => primary_institution})
28
+ def primary_institution=(new_primary_institution)
29
+ new_primary_institution = new_primary_institution.code if new_primary_institution.is_a?(Institutions::Institution)
30
+ self.user_attributes=({:primary_institution => new_primary_institution.to_sym})
32
31
  end
33
32
 
34
33
  def institutions
35
- return nil unless InstitutionList.institutions_defined?
36
34
  user_attributes[:institutions].collect { |institution|
37
- InstitutionList.instance.get(institution) } unless user_attributes.nil?
35
+ all_institutions[institution] } unless user_attributes.nil?
38
36
  end
39
37
 
40
- def institutions=(institutions)
41
- raise ArgumentError.new(
42
- "Institutions input should be an array.") unless institutions.is_a?(Array)
43
- filtered_institutions = institutions.collect { |institution|
44
- institution = institution.name if institution.is_a?(Institution)
45
- institution unless InstitutionList.instance.get(institution).nil?
38
+ def institutions=(new_institutions)
39
+ raise ArgumentError.new("Institutions input should be an array.") unless new_institutions.is_a?(Array)
40
+ new_institutions.collect! { |institution| institution.to_sym }
41
+ new_institutions.select! { |institution|
42
+ all_institutions[ new_institutions.is_a?(Institutions::Institution) ? institution.code : institution.to_sym]
46
43
  }
47
- self.user_attributes=({:institutions => filtered_institutions})
44
+ self.user_attributes=({:institutions => new_institutions}) unless new_institutions.empty?
48
45
  end
49
46
 
50
47
  # "Smart" updating of user_attributes. Maintains user_attributes that are not explicity overwritten.
@@ -54,13 +51,18 @@ module Authpds
54
51
  write_attribute(:user_attributes, (user_attributes || {}).merge(new_attributes))
55
52
  end
56
53
 
57
- # Returns a boolean based on whether the User has been refreshed recently.
54
+ # Returns a boolean based on whether the User has been refreshed recently.
58
55
  # If User#refreshed_at is older than User#expiration_date, the User is expired and the data
59
56
  # may need to be refreshed.
60
57
  def expired?
61
58
  # If the record is older than the expiration date, it is expired.
62
- (refreshed_at.nil?) ? true : refreshed_at < expiration_date
59
+ (refreshed_at.nil?) ? true : refreshed_at < expiration_date
63
60
  end
61
+
62
+ def all_institutions
63
+ Institutions.institutions
64
+ end
65
+ private :all_institutions
64
66
  end
65
67
  end
66
68
  end
@@ -2,73 +2,70 @@ module Authpds
2
2
  module Controllers
3
3
  module AuthpdsController
4
4
 
5
+ # Set helper methods when this module is included.
5
6
  def self.included(klass)
6
7
  klass.class_eval do
7
- include InstanceMethods
8
8
  helper_method :current_user_session, :current_user, :current_primary_institution
9
9
  end
10
10
  end
11
-
12
- module InstanceMethods
13
11
 
14
- # Get the current UserSession if it exists
15
- def current_user_session
16
- @current_user_session ||= UserSession.find
17
- end
12
+ # Get the current UserSession if it exists
13
+ def current_user_session
14
+ @current_user_session ||= UserSession.find
15
+ end
18
16
 
19
- # Get the current User if there is a UserSession
20
- def current_user
21
- @current_user ||= current_user_session.record unless current_user_session.nil?
22
- end
17
+ # Get the current User if there is a UserSession
18
+ def current_user
19
+ @current_user ||= current_user_session.record unless current_user_session.nil?
20
+ end
23
21
 
24
- # Determine current primary institution based on:
25
- # 0. institutions are not being used (returns nil)
26
- # 1. institution query string parameter in URL
27
- # 2. institution associated with the client IP
28
- # 3. primary institution for the current user
29
- # 4. first default institution
30
- def current_primary_institution
31
- @current_primary_institution ||=
32
- (InstitutionList.institutions_defined?) ?
33
- (params["#{institution_param_key}"].nil? or InstitutionList.instance.get(params["#{institution_param_key}"]).nil?) ?
34
- (primary_institution_from_ip.nil?) ?
35
- (current_user.nil? or current_user.primary_institution.nil?) ?
36
- InstitutionList.instance.defaults.first :
37
- current_user.primary_institution :
38
- primary_institution_from_ip :
39
- InstitutionList.instance.get(params["#{institution_param_key}"]) :
40
- nil
41
- end
22
+ # Determine current primary institution based on:
23
+ # 0. institutions are not being used (returns nil)
24
+ # 1. institution query string parameter in URL
25
+ # 2. institution associated with the client IP
26
+ # 3. primary institution for the current user
27
+ # 4. first default institution
28
+ def current_primary_institution
29
+ @current_primary_institution ||=
30
+ (institution_param.nil? or all_institutions[institution_param].nil?) ?
31
+ (primary_institution_from_ip.nil?) ?
32
+ (current_user.nil? or current_user.primary_institution.nil?) ?
33
+ Institutions.defaults.first :
34
+ current_user.primary_institution :
35
+ primary_institution_from_ip :
36
+ all_institutions[institution_param]
37
+ end
42
38
 
43
- # Grab the first institution that matches the client IP
44
- def primary_institution_from_ip
45
- InstitutionList.instance.institutions_with_ip(request.remote_ip).first unless request.nil?
46
- end
39
+ # Override to add institution.
40
+ def url_for(options={})
41
+ options[institution_param_key] ||= institution_param unless institution_param.nil?
42
+ super options
43
+ end
47
44
 
48
- # Determine institution layout based on:
49
- # 1. primary institution's resolve_layout
50
- # 2. default - views/layouts/application
51
- def institution_layout
52
- (current_primary_institution.nil? or current_primary_institution.application_layout.nil?) ?
53
- :application : current_primary_institution.application_layout
54
- end
45
+ def user_session_redirect_url(url)
46
+ (url.nil?) ? (request.referer.nil?) ? root_url : request.referer : url
47
+ end
55
48
 
56
- # Override to add institution.
57
- def url_for(options={})
58
- options["#{institution_param_key}"] =
59
- params["#{institution_param_key}"] unless params["#{institution_param_key}"].nil? or
60
- options["#{institution_param_key}"]
61
- super(options)
62
- end
49
+ # Grab the first institution that matches the client IP
50
+ def primary_institution_from_ip
51
+ Institutions.with_ip(request.remote_ip).first unless request.nil?
52
+ end
53
+ private :primary_institution_from_ip
63
54
 
64
- def user_session_redirect_url(url)
65
- (url.nil?) ? (request.referer.nil?) ? root_url : request.referer : url
66
- end
67
-
68
- def institution_param_key
69
- @institution_param_key ||= UserSession.institution_param_key
70
- end
55
+ def institution_param_key
56
+ @institution_param_key ||= UserSession.institution_param_key
57
+ end
58
+ private :institution_param_key
59
+
60
+ def institution_param
61
+ params["#{institution_param_key}"].to_sym unless params["#{institution_param_key}"].nil?
62
+ end
63
+ private :institution_param
64
+
65
+ def all_institutions
66
+ Institutions.institutions
71
67
  end
68
+ private :all_institutions
72
69
  end
73
70
  end
74
71
  end
@@ -2,28 +2,28 @@ module Authpds
2
2
  module Controllers
3
3
  module AuthpdsSessionsController
4
4
 
5
- # GET /user_sessions/new
6
- # GET /login
7
- def new
8
- @user_session = UserSession.new(params)
9
- redirect_to @user_session.login_url(params) unless @user_session.login_url.nil?
10
- raise RuntimeError.new( "Error in #{self.class}.\nNo login url defined") if @user_session.login_url.nil?
11
- end
5
+ # GET /user_sessions/new
6
+ # GET /login
7
+ def new
8
+ @user_session = UserSession.new(params)
9
+ redirect_to @user_session.login_url(params) unless @user_session.login_url.nil?
10
+ raise RuntimeError.new( "Error in #{self.class}.\nNo login url defined") if @user_session.login_url.nil?
11
+ end
12
12
 
13
- # GET /validate
14
- def validate
15
- @user_session = UserSession.create(params[:user_session])
16
- redirect_to (params[:return_url].nil?) ? root_url : params[:return_url]
17
- end
13
+ # GET /validate
14
+ def validate
15
+ @user_session = UserSession.create(params[:user_session])
16
+ redirect_to (params[:return_url].nil?) ? root_url : params[:return_url]
17
+ end
18
18
 
19
- # DELETE /user_sessions/1
20
- # GET /logout
21
- def destroy
22
- user_session = UserSession.find
23
- logout_url = user_session.logout_url(params) unless user_session.nil?
24
- user_session.destroy unless user_session.nil?
25
- redirect_to user_session_redirect_url(logout_url)
26
- end
19
+ # DELETE /user_sessions/1
20
+ # GET /logout
21
+ def destroy
22
+ user_session = UserSession.find
23
+ logout_url = user_session.logout_url(params) unless user_session.nil?
24
+ user_session.destroy unless user_session.nil?
25
+ redirect_to user_session_redirect_url(logout_url)
26
+ end
27
27
  end
28
28
  end
29
29
  end
@@ -4,6 +4,7 @@ module Authpds
4
4
  require 'nokogiri'
5
5
  require 'uri'
6
6
  require 'net/http'
7
+ require 'net/https'
7
8
 
8
9
  # Makes a call to the PDS get-attribute API.
9
10
  # Defaults attribute equal to "bor_info".
@@ -40,7 +41,6 @@ module Authpds
40
41
  # Makes a call get-attribute with attribute "bor_info".
41
42
  # Raises an exception if there is an unexpected response.
42
43
  class BorInfo < GetAttribute
43
-
44
44
  protected
45
45
  def initialize(pds_url, calling_system, pds_handle)
46
46
  super(pds_url, calling_system, pds_handle, "bor_info")
@@ -53,12 +53,6 @@ module Authpds
53
53
  self.class.send(:attr_reader, pds_attr)
54
54
  instance_variable_set("@#{pds_attr}".to_sym, xml_element.inner_text) unless xml_element.inner_text.nil?
55
55
  }
56
- # bor_info_attributes.each_value { |xml_element|
57
- # pds_attr = xml_element.gsub("-", "_")
58
- # self.class.send(:attr_reader, pds_attr)
59
- # instance_variable_set("@#{pds_attr}".to_sym,
60
- # @response.at("#{xml_element}").inner_text) unless @response.at("//bor-info/#{xml_element}").nil?
61
- # }
62
56
  end
63
57
  end
64
58
  end
@@ -1,12 +1,12 @@
1
1
  module Authpds
2
2
  # == Overview
3
3
  # The Authpds gem mixes in callbacks to Authlogic for persisting
4
- # sessions based on a valid PDS handle.
4
+ # sessions based on a valid PDS handle.
5
5
  # The module extends Authlogic and should be compatible with Authlogic configuation.
6
6
  # It also provides hooks for custom functionality.
7
7
  # The documentation below describes the hooks available, PDS config methods
8
8
  # and further details about the module.
9
- #
9
+ #
10
10
  # == Config Options Available
11
11
  # :pds_url:: Base pds url
12
12
  # :calling_system:: Name of the system (authpds)
@@ -17,7 +17,7 @@ module Authpds
17
17
  # :pds_record_identifier:: PDS user method to call to identify record
18
18
  # :institution_param_key:: Querystring parameter key for the institution value in this system
19
19
  # :validate_url_name:: URL name for validation action in routes (validate_url)
20
- #
20
+ #
21
21
  # == Hooks Available
22
22
  # :pds_record_identifier:: Allows for more complex logic in determining what should be used as the record identifier. Defaults to what was set in the pds_record_identifier config. Returns a Symbol.
23
23
  # :valid_sso_session?:: If there is no PDS handle, can we redirect to PDS to establish a SSO session based on some other information? Returns a Boolean.
@@ -25,15 +25,15 @@ module Authpds
25
25
  # :additional_attributes:: Allows for additional attributes to be stored in the record. Returns a Hash.
26
26
  # :expiration_date:: Indicates when the record information should be refreshed. Defaults to one week ago. Returns a Date or Time.
27
27
  #
28
- # == Further Implementation Details
28
+ # == Further Implementation Details
29
29
  # === Persisting a Session in AuthLogic
30
- # When persisting a Session, Authlogic attempts to create the Session based on information available
30
+ # When persisting a Session, Authlogic attempts to create the Session based on information available
31
31
  # without having to perform an actual login by calling the :persisting? method. Authologic provides several callbacks from the :persisting?
32
32
  # method, e.g. :before_persisting, :persist, :after_persisting. We're using the :persist callback and setting it to :persist_session.
33
- #
33
+ #
34
34
  # === Access to the controller in Session
35
- # The class that Session extends, Authologic::Session::Base, has an explicit handle to the current controller via the instance method
36
- # :controller. This gives our custom instance methods access to cookies, session information, loggers, etc. and also allows them to
35
+ # The class that Session extends, Authologic::Session::Base, has an explicit handle to the current controller via the instance method
36
+ # :controller. This gives our custom instance methods access to cookies, session information, loggers, etc. and also allows them to
37
37
  # perform redirects and renders.
38
38
  #
39
39
  # === :before_login vs. :login_url
@@ -43,6 +43,7 @@ module Authpds
43
43
  module Session
44
44
  def self.included(klass)
45
45
  klass.class_eval do
46
+ require 'institutions'
46
47
  extend Config
47
48
  include AuthpdsCallbackMethods
48
49
  include InstanceMethods
@@ -50,7 +51,7 @@ module Authpds
50
51
  persist :persist_session
51
52
  end
52
53
  end
53
-
54
+
54
55
  module Config
55
56
  # Base pds url
56
57
  def pds_url(value = nil)
@@ -106,8 +107,8 @@ module Authpds
106
107
  rw_config(:validate_url_name, value, "validate_url")
107
108
  end
108
109
  alias_method :validate_url_name=, :validate_url_name
109
- end
110
-
110
+ end
111
+
111
112
  module AuthpdsCallbackMethods
112
113
  # Hook for more complicated logic to determine PDS user record identifier
113
114
  def pds_record_identifier
@@ -118,7 +119,7 @@ module Authpds
118
119
  def valid_sso_session?
119
120
  return false
120
121
  end
121
-
122
+
122
123
  # Hook to provide additional authorization requirements
123
124
  def additional_authorization
124
125
  return true
@@ -128,13 +129,13 @@ module Authpds
128
129
  def additional_attributes
129
130
  {}
130
131
  end
131
-
132
+
132
133
  # Hook to update expiration date if necessary
133
134
  def expiration_date
134
135
  1.week.ago
135
136
  end
136
- end
137
-
137
+ end
138
+
138
139
  module InstanceMethods
139
140
  require "cgi"
140
141
 
@@ -154,7 +155,7 @@ module Authpds
154
155
  def logout_url(params={})
155
156
  return "#{self.class.pds_url}/pds?func=logout&url=#{CGI::escape(controller.user_session_redirect_url(self.class.redirect_logout_url))}"
156
157
  end
157
-
158
+
158
159
  # URL to redirect to in the case of establishing a SSO session.
159
160
  def sso_url(params={})
160
161
  return "#{self.class.pds_url}/pds?func=sso&institute=#{institution_attributes["link_code"]}&calling_system=#{self.class.calling_system}&url=#{CGI::escape(validate_url(params))}"
@@ -171,7 +172,7 @@ module Authpds
171
172
  return nil
172
173
  end
173
174
  end
174
-
175
+
175
176
  private
176
177
  def authenticated?
177
178
  authenticate
@@ -199,10 +200,10 @@ module Authpds
199
200
  # If PDS user is not nil (PDS session already established), authorize
200
201
  !pds_user.nil? && additional_authorization
201
202
  end
202
-
203
+
203
204
  # Get the record associated with this PDS user.
204
205
  def get_record(login)
205
- record = klass.find_by_smart_case_login_field(login)
206
+ record = klass.find_by_smart_case_login_field(login)
206
207
  record = klass.new login_field => login if record.nil?
207
208
  return record
208
209
  end
@@ -214,7 +215,7 @@ module Authpds
214
215
  # Do this part only if user data has expired.
215
216
  if self.attempted_record.expired?
216
217
  pds_attributes.each do |record_attr, pds_attr|
217
- self.attempted_record.send("#{record_attr}=".to_sym,
218
+ self.attempted_record.send("#{record_attr}=".to_sym,
218
219
  pds_user.send(pds_attr.to_sym)) if self.attempted_record.respond_to?("#{record_attr}=".to_sym)
219
220
  end
220
221
  pds_user.class.public_instance_methods(false).each do |pds_attr_reader|
@@ -224,10 +225,10 @@ module Authpds
224
225
  end
225
226
  self.attempted_record.user_attributes= additional_attributes
226
227
  end
227
-
228
- # Returns the URL for validating a UserSession on return from a remote login system.
229
- def validate_url(params={})
230
- url = controller.send(validate_url_name, :return_url => controller.user_session_redirect_url(params[:return_url]))
228
+
229
+ # Returns the URL for validating a UserSession on return from a remote login system.
230
+ def validate_url(params={})
231
+ url = controller.send(validate_url_name, :return_url => controller.user_session_redirect_url(params[:return_url]))
231
232
  return url if params.nil? or params.empty?
232
233
  url << "?" if url.match('\?').nil?
233
234
  params.each do |key, value|
@@ -235,18 +236,18 @@ module Authpds
235
236
  url << "&#{self.class.calling_system}_#{key}=#{value}"
236
237
  end
237
238
  return url
238
- end
239
-
239
+ end
240
+
240
241
  def validate_url_name
241
242
  @validate_url_name ||= self.class.validate_url_name
242
243
  end
243
244
 
244
245
  def institution_attributes
245
- @institution_attributes =
246
+ @institution_attributes =
246
247
  (controller.current_primary_institution.nil? or controller.current_primary_institution.login.nil?) ?
247
248
  {} : controller.current_primary_institution.login
248
249
  end
249
-
250
+
250
251
  def pds_attributes
251
252
  @pds_attributes ||= self.class.pds_attributes
252
253
  end