Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

Support CAS Protocol 3.0 attributes #2

Open
wants to merge 12 commits into
base: 0-3-stable
Choose a base branch
from

Conversation

jbjonesjr
Copy link

@jbjonesjr jbjonesjr commented Jul 16, 2016

CAS 2.0 just returned username and ticket in the authenticationSuccess message

<cas:authenticationSuccess>
 <cas:user>username</cas:user>
 <cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
</cas:authenticationSuccess>

CAS Protocol 3.0 supports additional attributes

<cas:authenticationSuccess>
     <cas:user>username</cas:user>
     <cas:attributes>
       <cas:firstname>John</cas:firstname>
       <cas:lastname>Doe</cas:lastname>
       <cas:title>Mr.</cas:title>
       <cas:email>jdoe@example.org</cas:email>
       <cas:affiliation>staff</cas:affiliation>
       <cas:affiliation>faculty</cas:affiliation>
     </cas:attributes>
     <cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
   </cas:authenticationSuccess>
 </cas:serviceResponse>

This pull request updates the parsing of the <authenticationSuccess> element to include attributes.

Actions

  • Update parsing to support attribute detection, inclusion in user_info
  • Update mock authenticationSuccess models to include attributes
  • Update tests to look for said attributes

* general code cleanup
* look for attributes element, new in CAS Protocol 3.0
  * if found, add those attributes into the user_info hash
@jbjonesjr
Copy link
Author

jbjonesjr commented Jul 16, 2016

This omniauth-cas module (built for omniauth-1.0) had already integrated this change from the original (omniauth 0.3 code that is in use here), so i'm i'm just going to port it over.

This work was done in these commits: dlindahl/omniauth-cas@8de378c , dlindahl/omniauth-cas@8ec97af

This model is of a CAS Protocol 3.0-style authenticationSuccess response that includes additional attributes. Details on the format can be found here: https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html#attributes-cas-30
@jbjonesjr
Copy link
Author

jbjonesjr commented Jul 17, 2016

- abstract user_info a bit with raw_info
- build out the proper hash for the omniauth schema requirements
- expose uid in a managed way
@jbjonesjr
Copy link
Author

Note, the CAS success mock defined dlindahl/omniauth-cas@8ec97af and previously in our test suite is not valid schema. The only valid elements under success (per the schema linked above for 2.0) was User, ProxyGrantingTicket and Proxies.

  <xs:complexType name="AuthenticationSuccessType">
        <xs:sequence>
            <xs:element name="user" type="xs:string"/>
            <xs:element name="proxyGrantingTicket" type="xs:string" minOccurs="0"/>
            <xs:element name="proxies" type="cas:ProxiesType" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>

I'm updating the Protocol 2.0 tests (and spec/mock) to reflect this.

@jbjonesjr
Copy link
Author

jbjonesjr commented Aug 6, 2016

cc/ @github/platform-iam

I believe this PR would fall under your purview. I'm pretty satisfied with the code as is, but can't figure out how we expect rspec to run appropriately. I'd feel 💯% better sending you a PR with passing tests, but am about at the end of my capability.

{}.tap do |hash|
node.children.each do |e|
node_name = e.name.sub(/^cas:/, '')
unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanup @jbjonesjr! If we wanted to be even nitpickier, maybe use a next if here instead of the unless block for readability?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davesims I copied this method from the upstream code. I have no clue as to our plan to ever sync with upstream again, so am not sure how much we care about being compatible. Thoughts?

@davesims
Copy link

davesims commented Aug 8, 2016

Thanks for this @jbjonesjr! I took a quick first pass on it, I'll do more thorough 👀 later this week. Looks great so far.

attr_accessor :raw_info
alias_method :user_info, :raw_info

option :uid_key, 'user'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the option method is available in modern implementations of OmniAuth::Strategy, it is not available in the github/omniauth version
Need to revert back to a regular attr_accessor with a default.

@jbjonesjr
Copy link
Author

So I figured out how to run the tests, am working my way through confirming that this actually works. Will commit and make a comment when ready to go again

@jbjonesjr
Copy link
Author

jbjonesjr commented Sep 5, 2016

Woot woot!
image

Finished in 0.12449 seconds
18 examples, 1 failure

Failed examples:

rspec ./spec/omniauth/strategies/cas_spec.rb:129 # OmniAuth::Strategies::CAS GET /auth/cas/callback with a valid ticket and gzipped response from the server on ruby >1.8 should call through to the master app when response is gzipped

Got almost all the tests passing (pretty sure the failing tests failed before I started this experiment).
Note: Confirmed afterwards that the gzip test fails on the 0-3-stable branch as well

I need to now go back and make sure this is compatible with the omniauth.auth variable the app expects us this module to provide (I'm worried we might not be sticking to the Auth Hash Schema. I'm also going to add some comments and discussion around some of these changes to be document why I did things the way I did, and hopefully provide the needed care to 🚢.

Copy link
Author

@jbjonesjr jbjonesjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotated my changes for this codebase


@uid_key = 'user'

def userHash
Copy link
Author

@jbjonesjr jbjonesjr Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add fields defined in the omniauth user_info hash. support multiple formats that it can be shared via

def initialize(app, options = {}, &block)
super(app, options[:name] || :cas, options.dup, &block)
@configuration = OmniAuth::Strategies::CAS::Configuration.new(options)
@configuration = OmniAuth::Strategies::CAS::Configuration.new(options)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just whitespace change

@ticket = request.params['ticket']
return fail!(:no_ticket, 'No CAS Ticket') unless @ticket

self.raw_info = ServiceTicketValidator.new(@configuration, callback_url, @ticket).user_info
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the raw_info accessor, also chain methods for readability

super
end

def auth_hash
OmniAuth::Utils.deep_merge(super, {
'uid' => @user_info.delete('user'),
'extra' => @user_info
'uid' => @raw_info['user'],
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configure omniauth object as defined in Auth Hash Schema


# Deletes Hash pairs with `nil` values.
# From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
def prune!(hash)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up nil/null values

to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'cas_success.xml')))
get '/auth/cas/callback?ticket=593af'
end
shared_examples :successful_validation do
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upstream tests are a bit cleaner and easier to follow.

get "/auth/cas/callback?ticket=593af&url=#{return_url}"
end

it 'should strip the ticket parameter from the callback URL before sending it to the CAS server' do
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of these tests are from upstream, but some of my own intepretation based on our sample success criteria

let(:return_url) { 'http://127.0.0.10/?some=parameter' }


context 'cas-protocol-2.0' do
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support the two different protocols under test

end
stub_request(:get, /^https:\/\/cas.example.org(:443)?\/serviceValidate\?([^&]+&)?ticket=593af/).
stub_request(:get, /^http(s)?:\/\/cas.example.org(:8080)?\/serviceValidate\?([^&]+&)?ticket=593af/).
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above.

context 'cas-protocol-3.0' do
let(:xml_file_name) { 'cas_success_3.0.xml' }
it_behaves_like :successful_validation
end
end

unless RUBY_VERSION =~ /^1\.8\.\d$/
describe 'GET /auth/cas/callback with a valid ticket and gzipped response from the server on ruby >1.8' do
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this test fails on my branch, and on the branch i branched from.

¯_(ツ)_/¯

@jbjonesjr
Copy link
Author

OK 👌 . I've taken advantage of the new code review functionality to annotate the work that I have performed in this PR. I'm happy to answer any questions that anyone may have.

cc/ @sbryant as some folks thought you might be an owner of this sort of thing.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
2 participants