trivialsso 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +7 -0
- data/README.md +103 -0
- data/Rakefile +7 -0
- data/lib/generators/templates/README +9 -0
- data/lib/generators/templates/sso_secret.rb +5 -0
- data/lib/generators/trivialsso/install_generator.rb +20 -0
- data/lib/trivialsso/version.rb +3 -0
- data/lib/trivialsso.rb +110 -0
- data/test/test_trivialsso.rb +96 -0
- data/trivialsso.gemspec +29 -0
- metadata +81 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 David J. Lee. http://lee.dj/
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
## Trivialsso
|
2
|
+
|
3
|
+
A very simple gem to help with creating and reading cookies across multiple sites in a Ruby on Rails application.
|
4
|
+
|
5
|
+
This allows for a simple single sign on solution among sites within the same domain.
|
6
|
+
|
7
|
+
This does *not* work across domains.
|
8
|
+
|
9
|
+
|
10
|
+
## Getting Started
|
11
|
+
|
12
|
+
Add the gem to your Gemfile
|
13
|
+
|
14
|
+
gem 'trivialsso'
|
15
|
+
|
16
|
+
Install the gem
|
17
|
+
|
18
|
+
bundle install
|
19
|
+
|
20
|
+
After you've installed the gem, you need to generate a configuration file.
|
21
|
+
|
22
|
+
rails g trivialsso:install
|
23
|
+
|
24
|
+
This will create an initializer file with a shared secret. You need to modify this to a big long
|
25
|
+
string of characters. Keep this safe from others as they could forge cookies for your sites if they
|
26
|
+
get ahold of this string. All sites that use the single sign on must have this same shared secret
|
27
|
+
for the cookies to properly interoperate.
|
28
|
+
|
29
|
+
## Creating a cookie
|
30
|
+
|
31
|
+
A cookie is created using a hash of data supplied to it. This must contain a "username" key.
|
32
|
+
|
33
|
+
When you create the cookie data an expire time is built into the payload. Setting the :expires on the cookie is
|
34
|
+
just a convenience to make sure it gets cleared by the browser. The actual expiration date that matters is what is encoded in the cookie.
|
35
|
+
|
36
|
+
|
37
|
+
# Create a hash of data we want to store in the cookie.
|
38
|
+
userdata = {"username" => current_user.login, "display" => current_user.display_name, "groups" => current_user.memberof}
|
39
|
+
|
40
|
+
#Generate the cookie data
|
41
|
+
cookie = Trivialsso::Login.cookie(userdata)
|
42
|
+
|
43
|
+
# Set the cookie
|
44
|
+
cookies[:sso_login] = {
|
45
|
+
:value => cookie,
|
46
|
+
:expires => Trivialsso::Login.expire_date,
|
47
|
+
:domain => 'mydomain.com',
|
48
|
+
:httponly => true,
|
49
|
+
}
|
50
|
+
|
51
|
+
The above code creates a hash of data we will be putting in the cookie, generates the cookie, and then sets the cookie in the browser.
|
52
|
+
|
53
|
+
## Decoding a cookie
|
54
|
+
|
55
|
+
Retrieve the contents of the cookie by calling decode_cookie
|
56
|
+
|
57
|
+
@userdata = Trivialsso::Login.decode_cookie(cookies[:sso_login])
|
58
|
+
|
59
|
+
This will throw an exception if the cookie has been tampered with, or if the expiration date has passed.
|
60
|
+
|
61
|
+
## Sample code for application_controller
|
62
|
+
|
63
|
+
Here are some methods you can add into your application controller to authenticate against the cookie.
|
64
|
+
|
65
|
+
# If there is a problem with the cookie, redirect back to our central login server.
|
66
|
+
rescue_from Trivialsso::CookieError do |exception|
|
67
|
+
redirect_to 'https://login.mydomain.com/'
|
68
|
+
end
|
69
|
+
|
70
|
+
# authorize our users based on the cookie.
|
71
|
+
before_filter :auth_user!
|
72
|
+
|
73
|
+
# authenticate a user and set @current_user
|
74
|
+
def auth_user!
|
75
|
+
cu = current_user
|
76
|
+
|
77
|
+
# Check for authorization based on "groups" data that was put in the cookie
|
78
|
+
# by the central login application.
|
79
|
+
# you could also skip this check and just return true if the cookie is valid.
|
80
|
+
if cu['groups'].include? "ALLOWED_GROUP" #all lower case
|
81
|
+
@current_user = cu
|
82
|
+
return true
|
83
|
+
else
|
84
|
+
render :file => "#{Rails.root}/public/403", :formats => [:html], :status => 403, :layout => false
|
85
|
+
end
|
86
|
+
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
|
90
|
+
# our current_user decodes the cookie.
|
91
|
+
def current_user
|
92
|
+
Trivialsso::Login.decode_cookie(cookies[:ophth_login])
|
93
|
+
end
|
94
|
+
|
95
|
+
# Define the name we want to record in paper_trail (if using)
|
96
|
+
def user_for_paper_trail
|
97
|
+
if @current_user.blank?
|
98
|
+
return "anonymous"
|
99
|
+
else
|
100
|
+
return @current_user['username']
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
What you need to do next:
|
4
|
+
|
5
|
+
Edit config/initalizers/sso_secret.rb to include a secret that will
|
6
|
+
be shared between all of the sites. This secret must match for each
|
7
|
+
site using the cookies.
|
8
|
+
|
9
|
+
===============================================================================
|
@@ -0,0 +1,5 @@
|
|
1
|
+
# This file is required to define the shared secret used for generating and decoding
|
2
|
+
# SSO cookies. This secret *must* be the same across all of your applications.
|
3
|
+
|
4
|
+
# TODO, figure out how to get app_name
|
5
|
+
<%= Rails.application.class.parent_name %>::Application.config.sso_secret = "You-really-need-to-change-this-to-a-nice-long-random-string"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Trivialsso
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates",__FILE__)
|
5
|
+
|
6
|
+
desc "Creates a sso_secret initalizer"
|
7
|
+
|
8
|
+
def copy_intializer
|
9
|
+
#template - maybe use that in the future?
|
10
|
+
template "sso_secret.rb", "config/initializers/sso_secret.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def show_readme
|
15
|
+
readme "README"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/trivialsso.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require "trivialsso/version"
|
2
|
+
|
3
|
+
module Trivialsso
|
4
|
+
class Login
|
5
|
+
# signing and un-signing may have to be refactored to use OpenSSL:HMAC...
|
6
|
+
# as this will not work well across platforms. (ideally this should work for PHP as well)
|
7
|
+
|
8
|
+
# create an encrypted and signed cookie containing userdata and an expiry date.
|
9
|
+
# userdata should be an array, and at minimum include a 'username' key.
|
10
|
+
# using json serializer to hopefully allow future cross version compatibliity
|
11
|
+
# (Marshall, the default serializer, is not compatble between versions)
|
12
|
+
def self.cookie(userdata, exp_date = expire_date)
|
13
|
+
begin
|
14
|
+
raise MissingConfig if Rails.configuration.sso_secret.blank?
|
15
|
+
rescue NoMethodError
|
16
|
+
raise MissingConfig
|
17
|
+
end
|
18
|
+
raise NoUsernameCookie if userdata['username'].blank?
|
19
|
+
enc = ActiveSupport::MessageEncryptor.new(Rails.configuration.sso_secret, :serializer => JSON)
|
20
|
+
cookie = enc.encrypt_and_sign([userdata,exp_date.to_i])
|
21
|
+
return cookie
|
22
|
+
end
|
23
|
+
|
24
|
+
# Decodes and verifies an encrypted cookie
|
25
|
+
# throw a proper exception if a bad or invalid cookie.
|
26
|
+
# otherwise, return the username and userdata stored in the cookie
|
27
|
+
def self.decode_cookie(cookie)
|
28
|
+
begin
|
29
|
+
raise MissingConfig if Rails.configuration.sso_secret.blank?
|
30
|
+
rescue NoMethodError
|
31
|
+
raise MissingConfig
|
32
|
+
end
|
33
|
+
if cookie.blank?
|
34
|
+
raise MissingCookie
|
35
|
+
else
|
36
|
+
enc = ActiveSupport::MessageEncryptor.new(Rails.configuration.sso_secret, :serializer => JSON)
|
37
|
+
begin
|
38
|
+
userdata, timestamp = enc.decrypt_and_verify(cookie)
|
39
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
40
|
+
#raise our own cookie error instead of passing on invalid signature.
|
41
|
+
raise BadCookie
|
42
|
+
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
43
|
+
#raise our own cookie error instead of passing on invalid message.
|
44
|
+
raise BadCookie
|
45
|
+
end
|
46
|
+
|
47
|
+
# Determine how many seconds our cookie is valid for.
|
48
|
+
timeRemain = timestamp - DateTime.now.to_i
|
49
|
+
|
50
|
+
#make sure current time is not past timestamp.
|
51
|
+
if timeRemain > 0
|
52
|
+
return userdata
|
53
|
+
else
|
54
|
+
raise LoginExpired
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#returns the exipiry date from now. Used for setting an expiry date when creating cookies.
|
60
|
+
def self.expire_date
|
61
|
+
9.hours.from_now
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
#
|
67
|
+
# Error Classes
|
68
|
+
#
|
69
|
+
# General Cookie Error
|
70
|
+
class CookieError < RuntimeError
|
71
|
+
def to_s
|
72
|
+
"There was an error processing the Ophth Cookie"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cookie can not be verified, data has been altered
|
77
|
+
class BadCookie < CookieError
|
78
|
+
def to_s
|
79
|
+
"Login cookie can not be verified!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# cookie is no longer valid
|
84
|
+
class LoginExpired < CookieError
|
85
|
+
def to_s
|
86
|
+
"Login cookie has expired!"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Cookie is missing
|
91
|
+
class MissingCookie < CookieError
|
92
|
+
def to_s
|
93
|
+
"Login cookie is missing!"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Cookie is lacking a username.
|
98
|
+
class NoUsernameCookie < CookieError
|
99
|
+
def to_s
|
100
|
+
"Need username to create cookie"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Missing configuration value.
|
105
|
+
class MissingConfig < CookieError
|
106
|
+
def to_s
|
107
|
+
"Missing secret configuration for cookie, need to define config.sso_secret"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require 'rails/all'
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'mocha' # Need the mocha gem to properly stub out rails configuration.
|
6
|
+
require 'trivialsso'
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
# Test suite for Trivialsso. Which requires rails (for config values) and active_support for time calculations.
|
11
|
+
#
|
12
|
+
class TrivialssoTest < Test::Unit::TestCase
|
13
|
+
|
14
|
+
def setup
|
15
|
+
# Stub out our Rails config so we can test things properly.
|
16
|
+
Rails.stubs(:configuration).returns(Rails::Application::Configuration.allocate)
|
17
|
+
Rails.configuration.sso_secret = "57f236fdb162bd951f2ed15683a9d9d327f26ecdccb1897107161b76223b07a46d34907c3357e66b4eb0ef6e06888a700c0dc"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def test_can_create_cookie
|
22
|
+
mycookie = Trivialsso::Login.cookie({'username' => 'testor'})
|
23
|
+
assert !mycookie.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_create_cookie_with_userdata
|
27
|
+
mycookie = Trivialsso::Login.cookie({'username' => 'testor', 'data' => 'additional cookie data'})
|
28
|
+
assert !mycookie.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_create_cookie_and_decode_it
|
32
|
+
mycookie = Trivialsso::Login.cookie({'username' => 'testor', 'data' => 'additional cookie data'})
|
33
|
+
data = Trivialsso::Login.decode_cookie(mycookie)
|
34
|
+
|
35
|
+
if data['data'] == 'additional cookie data'
|
36
|
+
assert true
|
37
|
+
else
|
38
|
+
assert false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_throw_exception_on_missing_username
|
43
|
+
begin
|
44
|
+
mycookie = Trivialsso::Login.cookie("")
|
45
|
+
rescue Trivialsso::NoUsernameCookie
|
46
|
+
assert true, "Exception thrown due to blank username"
|
47
|
+
else
|
48
|
+
assert false, "No exception was raised for a blank username"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def test_expire_date_exists
|
54
|
+
# in a full rails environment, this will return an ActiveSupport::TimeWithZone
|
55
|
+
mydate = Trivialsso::Login.expire_date
|
56
|
+
assert mydate.is_a?(Time), "proper Time object not returned"
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_expire_date_is_in_future
|
60
|
+
assert (DateTime.now < Trivialsso::Login.expire_date), "Expire date is in the past - cookie will expire immediatly."
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_exception_on_blank_cookie
|
64
|
+
begin
|
65
|
+
Trivialsso::Login.decode_cookie("")
|
66
|
+
rescue Trivialsso::MissingCookie
|
67
|
+
assert true
|
68
|
+
else
|
69
|
+
assert false, "no exception was raised for a blank cookie"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_exception_on_bad_cookie
|
74
|
+
begin
|
75
|
+
Trivialsso::Login.decode_cookie("BAhbB0kiC2RqbGVlMgY6BkVUbCsHo17iTg")
|
76
|
+
rescue Trivialsso::BadCookie
|
77
|
+
assert true
|
78
|
+
else
|
79
|
+
assert false, "no exception was raised for a malformed cookie"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_exception_on_expired_cookie
|
84
|
+
#create a cookie that is expired.
|
85
|
+
temp_cookie = Trivialsso::Login.cookie({'username' => 'testor'}, 2.seconds.ago)
|
86
|
+
|
87
|
+
begin
|
88
|
+
Trivialsso::Login.decode_cookie(temp_cookie)
|
89
|
+
rescue Trivialsso::LoginExpired
|
90
|
+
assert true
|
91
|
+
else
|
92
|
+
assert false, "no exception was raised for an expired cookie"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/trivialsso.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "trivialsso/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "trivialsso"
|
7
|
+
s.version = Trivialsso::VERSION
|
8
|
+
s.authors = ["David J. Lee"]
|
9
|
+
s.email = ["david@lee.dj"]
|
10
|
+
s.homepage = "https://github.com/DavidJLee/trivialsso"
|
11
|
+
s.summary = "A simple library to help with Single Sign On cookies"
|
12
|
+
s.description = "Used to encode and decode cookies used in a single sign on implementation within the same domain"
|
13
|
+
|
14
|
+
s.rubyforge_project = "trivialsso"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rails"
|
24
|
+
|
25
|
+
# Need at least 3.2.0 to support the JSON serialization.
|
26
|
+
s.add_dependency "rails", "~> 3.2.0"
|
27
|
+
s.add_dependency "activesupport", "~> 3.2.0"
|
28
|
+
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trivialsso
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David J. Lee
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-08 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &70357652379460 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70357652379460
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70357652378960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.2.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70357652378960
|
36
|
+
description: Used to encode and decode cookies used in a single sign on implementation
|
37
|
+
within the same domain
|
38
|
+
email:
|
39
|
+
- david@lee.dj
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- MIT-LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/generators/templates/README
|
50
|
+
- lib/generators/templates/sso_secret.rb
|
51
|
+
- lib/generators/trivialsso/install_generator.rb
|
52
|
+
- lib/trivialsso.rb
|
53
|
+
- lib/trivialsso/version.rb
|
54
|
+
- test/test_trivialsso.rb
|
55
|
+
- trivialsso.gemspec
|
56
|
+
homepage: https://github.com/DavidJLee/trivialsso
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: trivialsso
|
76
|
+
rubygems_version: 1.8.6
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A simple library to help with Single Sign On cookies
|
80
|
+
test_files:
|
81
|
+
- test/test_trivialsso.rb
|