verge 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +68 -0
- data/VERSION +1 -0
- data/lib/verge/crypto.rb +15 -0
- data/lib/verge/server/base.rb +74 -0
- data/lib/verge/server/models.rb +156 -0
- data/lib/verge/server/views/token.js.erb +29 -0
- data/lib/verge.rb +14 -0
- data/spec/crypto_spec.rb +11 -0
- data/spec/factories.rb +17 -0
- data/spec/models_spec.rb +148 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/verge_spec.rb +128 -0
- data/verge.gemspec +87 -0
- metadata +157 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Adam Elliot
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= Verge
|
2
|
+
|
3
|
+
Lightweight centralized authentications system.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2009 Adam Elliot. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "verge"
|
8
|
+
gem.summary = %Q{Lightweight centralized authentication system built on Sinatra}
|
9
|
+
gem.description = %Q{Simple system that grants trusted sites tokens if users have successfully authenticated. So they are free to interact with each other securely.}
|
10
|
+
gem.email = "adam@wartube.com"
|
11
|
+
gem.homepage = "http://github.com/adamelliot/verge"
|
12
|
+
gem.authors = ["Adam Elliot"]
|
13
|
+
gem.add_dependency "sinatra"
|
14
|
+
gem.add_dependency "datamapper"
|
15
|
+
gem.add_dependency "bcrypt-ruby"
|
16
|
+
gem.add_dependency "activesupport"
|
17
|
+
gem.add_development_dependency "rspec"
|
18
|
+
gem.add_development_dependency "factory_girl"
|
19
|
+
gem.add_development_dependency "rack-test"
|
20
|
+
gem.add_development_dependency "do_sqlite3"
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
29
|
+
spec.spec_opts = %W{--options \"#{File.dirname(__FILE__)}/spec/spec.opts\"}
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
35
|
+
spec.spec_opts = %W{--options \"#{File.dirname(__FILE__)}/spec/spec.opts\"}
|
36
|
+
spec.libs << 'lib' << 'spec'
|
37
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
38
|
+
spec.rcov = true
|
39
|
+
spec.rcov_opts = lambda do
|
40
|
+
IO.readlines("#{File.dirname(__FILE__)}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :spec => :check_dependencies
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'reek/rake_task'
|
48
|
+
Reek::RakeTask.new do |t|
|
49
|
+
t.fail_on_error = true
|
50
|
+
t.verbose = false
|
51
|
+
t.source_files = 'lib/**/*.rb'
|
52
|
+
end
|
53
|
+
rescue LoadError
|
54
|
+
task :reek do
|
55
|
+
abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
task :default => :spec
|
60
|
+
|
61
|
+
begin
|
62
|
+
require 'yard'
|
63
|
+
YARD::Rake::YardocTask.new
|
64
|
+
rescue LoadError
|
65
|
+
task :yardoc do
|
66
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
67
|
+
end
|
68
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/verge/crypto.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
3
|
+
module Verge
|
4
|
+
module Crypto
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def token # nodoc #
|
8
|
+
Digest::SHA512.hexdigest((1..10).collect{ rand.to_s }.join + Time.now.usec.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def digest(*values) # nodoc #
|
12
|
+
Digest::SHA512.hexdigest values.join
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Verge
|
5
|
+
module Server
|
6
|
+
class Base < Sinatra::Base
|
7
|
+
enable :logging
|
8
|
+
set :root, File.dirname(__FILE__)
|
9
|
+
|
10
|
+
configure :development do
|
11
|
+
enable :dump_errors
|
12
|
+
end
|
13
|
+
|
14
|
+
# Request from clients (browers generally) to authenticate with username
|
15
|
+
# and password. Returns a token that should be sent back to the site
|
16
|
+
# along with the login passed here to be verified by the site
|
17
|
+
# as allowed to login.
|
18
|
+
get '/auth' do
|
19
|
+
extract_site
|
20
|
+
|
21
|
+
user = User.authenticate(params[:login], params[:password])
|
22
|
+
halt 401, "Bad user." if user.nil?
|
23
|
+
|
24
|
+
set_cookie_for_user(user)
|
25
|
+
user.token.value
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/token.js' do
|
29
|
+
@token = request.cookies["token"]
|
30
|
+
@login = request.cookies["login"]
|
31
|
+
|
32
|
+
erb 'token.js'.to_sym unless @token.nil? || @token.blank? || @login.nil? || @login.blank?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates user accounts
|
36
|
+
post '/create' do
|
37
|
+
extract_site
|
38
|
+
|
39
|
+
user = User.new(:login => params[:login], :password => params[:password])
|
40
|
+
halt 400, "Could not create user." unless user.save
|
41
|
+
|
42
|
+
# TODO: Make user activation not manditory, for now ther is no activation mechanism
|
43
|
+
user.activate!
|
44
|
+
|
45
|
+
user.token.value
|
46
|
+
end
|
47
|
+
|
48
|
+
# Verifies if a token passed to a site is valid and checks that it
|
49
|
+
# properly matches the user requesting it.
|
50
|
+
#
|
51
|
+
# This is called from the sites themselves to verify that the token
|
52
|
+
# being passed from the client isn't spoofed and can be trusted to
|
53
|
+
# be the user they say they are.
|
54
|
+
get '/verify/:token' do |token|
|
55
|
+
signed_token = extract_site.signed_tokens.first(:value => token)
|
56
|
+
halt 404, "Token not found." if signed_token.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def extract_site # nodoc #
|
62
|
+
site = Site.find_by_url(request.referer)
|
63
|
+
(site.nil? && halt(401, "Not a valid site.")) || site
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_cookie_for_user(user) # nodoc #
|
67
|
+
response.set_cookie("token", {
|
68
|
+
:value => user.token.value,
|
69
|
+
:path => '/'
|
70
|
+
})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-aggregates'
|
3
|
+
require 'dm-validations'
|
4
|
+
require 'dm-types'
|
5
|
+
require 'dm-timestamps'
|
6
|
+
|
7
|
+
require 'activesupport'
|
8
|
+
|
9
|
+
DataMapper::Logger.new("#{Dir.pwd}/../log/dm.log", :debug)
|
10
|
+
DataMapper::setup(:default, ENV['DATABASE_URL'] || "sqlite3:///#{Dir.pwd}/../db.sqlite3")
|
11
|
+
|
12
|
+
module Verge
|
13
|
+
module Server
|
14
|
+
class User
|
15
|
+
include DataMapper::Resource
|
16
|
+
|
17
|
+
property :id, Serial, :key => true
|
18
|
+
property :login, String, :nullable => false, :length => 1..255
|
19
|
+
property :password, BCryptHash
|
20
|
+
property :activated, Boolean, :default => false, :nullable => false
|
21
|
+
property :expiry, DateTime, :default => lambda { DateTime.now + 1.day }
|
22
|
+
|
23
|
+
has n, :tokens, :expiry.gt => DateTime.now
|
24
|
+
|
25
|
+
validates_is_unique :login
|
26
|
+
|
27
|
+
before :destroy, :destroy_tokens
|
28
|
+
|
29
|
+
# Generates a new token for this user.
|
30
|
+
def generate_token(expiry = nil)
|
31
|
+
tokens.create(expiry && {:expiry => expiry} || {})
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a valid token for this user. If no tokens exist on is created
|
35
|
+
def token
|
36
|
+
tokens.count > 0 ? tokens.first : generate_token
|
37
|
+
end
|
38
|
+
|
39
|
+
# Marks this user as valid. Invalid users will be destroyed after their
|
40
|
+
# expiry passes.
|
41
|
+
def activate!
|
42
|
+
activated = true
|
43
|
+
expiry = nil
|
44
|
+
save
|
45
|
+
end
|
46
|
+
|
47
|
+
# Attempts to find a user based on the credentials passed.
|
48
|
+
def self.authenticate(login, password)
|
49
|
+
u = User.first(:login => login)
|
50
|
+
(u.nil? || u.password != password) && nil || u
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes expired users
|
54
|
+
def self.remove_expired_users
|
55
|
+
User.all(:expiry.lt => DateTime.now).destroy
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def destroy_tokens
|
61
|
+
tokens.all.destroy
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class SignedToken
|
66
|
+
include DataMapper::Resource
|
67
|
+
|
68
|
+
property :id, Serial, :key => true
|
69
|
+
property :value, String, :length => 128..128, :nullable => false
|
70
|
+
|
71
|
+
belongs_to :token
|
72
|
+
belongs_to :site
|
73
|
+
|
74
|
+
validates_is_unique :value
|
75
|
+
end
|
76
|
+
|
77
|
+
class Token
|
78
|
+
TERMINAL_EPOCH = DateTime.new(4000) # Distant future
|
79
|
+
|
80
|
+
include DataMapper::Resource
|
81
|
+
|
82
|
+
property :id, Serial, :key => true
|
83
|
+
property :value, String, :length => 128..128, :default => lambda { Verge::Crypto.token }
|
84
|
+
property :expiry, DateTime, :nullable => false, :default => TERMINAL_EPOCH
|
85
|
+
|
86
|
+
belongs_to :user
|
87
|
+
has n, :signed_tokens
|
88
|
+
|
89
|
+
before :destroy, :destroy_signed_tokens
|
90
|
+
after :create, :sign
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def sign # nodoc #
|
95
|
+
Site.all.each do |site|
|
96
|
+
site.sign_token(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy_signed_tokens # nodoc #
|
101
|
+
signed_tokens.destroy
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Site
|
106
|
+
include DataMapper::Resource
|
107
|
+
|
108
|
+
property :id, Serial, :key => true
|
109
|
+
property :domain, String, :length => 3..300
|
110
|
+
property :signature, String, :length => 128..128, :default => lambda { Verge::Crypto.token }
|
111
|
+
|
112
|
+
has n, :signed_tokens
|
113
|
+
|
114
|
+
validates_is_unique :domain
|
115
|
+
validates_is_unique :signature
|
116
|
+
|
117
|
+
before :destroy, :destroy_signed_tokens
|
118
|
+
after :create, :sign_tokens
|
119
|
+
|
120
|
+
# Prepends this sites token to the string list and runs a digest over
|
121
|
+
# the new string.
|
122
|
+
def sign(*args)
|
123
|
+
Verge::Crypto.digest(signature, args.join)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Takes a token for a user and signs it creating a new hash
|
127
|
+
def sign_token(token)
|
128
|
+
signed_tokens.create(:value => sign(token.user.login, token.value), :token => token)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Searchs all the sites for ones that match the protocol and domain
|
132
|
+
# EG:
|
133
|
+
# "http://verge.example.com/some/path?id=1" will match
|
134
|
+
# "verge.example.com"
|
135
|
+
def self.find_by_url(url)
|
136
|
+
return nil if url.nil?
|
137
|
+
domain = url[/^([A-Za-z\d]*(:\/\/)){0,1}([^\/]*)/, 3]
|
138
|
+
Site.first(:domain => domain)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def sign_tokens # nodoc #
|
144
|
+
Token.all(:expiry.gt => DateTime.now).each do |token|
|
145
|
+
sign_token(token)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def destroy_signed_tokens # nodoc #
|
150
|
+
signed_tokens.destroy
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
DataMapper.auto_migrate!
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
(function() {
|
2
|
+
function announceToken() {
|
3
|
+
var login = "<%= @login %>";
|
4
|
+
var token = "<%= @token %>";
|
5
|
+
|
6
|
+
// For now we'll just write the server's token to the console, in the
|
7
|
+
// future we'll need to actually do something with it.
|
8
|
+
if (console) {
|
9
|
+
console.log("Login: " + login);
|
10
|
+
console.log("Token: " + token);
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
(function() {
|
15
|
+
if (document.addEventListener) {
|
16
|
+
document.addEventListener("DOMContentLoaded", function() {
|
17
|
+
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
|
18
|
+
announceToken();
|
19
|
+
}, false);
|
20
|
+
} else if (document.attachEvent) {
|
21
|
+
document.attachEvent("onreadystatechange", function() {
|
22
|
+
if (document.readyState == "complete") {
|
23
|
+
document.detachEvent("onreadystatechange", argument.callee);
|
24
|
+
announceToken();
|
25
|
+
}
|
26
|
+
});
|
27
|
+
}
|
28
|
+
})();
|
29
|
+
})();
|
data/lib/verge.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
module Verge
|
4
|
+
autoload :Crypto, "verge/crypto"
|
5
|
+
|
6
|
+
module Server
|
7
|
+
autoload :Base, "verge/server/base"
|
8
|
+
|
9
|
+
autoload :User, "verge/server/models"
|
10
|
+
autoload :SignedToken, "verge/server/models"
|
11
|
+
autoload :Token, "verge/server/models"
|
12
|
+
autoload :Site, "verge/server/models"
|
13
|
+
end
|
14
|
+
end
|
data/spec/crypto_spec.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Verge::Crypto do
|
4
|
+
it "returns a sha512 token" do
|
5
|
+
Verge::Crypto.token.length.should == 128
|
6
|
+
end
|
7
|
+
|
8
|
+
it "creates a hash for multiple values entered" do
|
9
|
+
Verge::Crypto.digest("some", "values").length.should == 128
|
10
|
+
end
|
11
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Factory.sequence(:login) { |n| "verge-#{n}" }
|
2
|
+
Factory.sequence(:domain) { |n| "site-#{n}.com" }
|
3
|
+
|
4
|
+
Factory.define(:user, :class => Verge::Server::User) do |u|
|
5
|
+
u.login { Factory.next(:login) }
|
6
|
+
u.password '0rbital'
|
7
|
+
end
|
8
|
+
|
9
|
+
Factory.define(:site, :class => Verge::Server::Site) do |s|
|
10
|
+
s.domain { Factory.next(:domain) }
|
11
|
+
end
|
12
|
+
|
13
|
+
Factory.define(:signed_token, :class => Verge::Server::SignedToken) do |s|
|
14
|
+
s.token_id 1
|
15
|
+
s.site_id 1
|
16
|
+
s.value { Verge::Crypto.token }
|
17
|
+
end
|
data/spec/models_spec.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Verge::Server::User do
|
4
|
+
before :each do
|
5
|
+
Verge::Server::Token.all.destroy!
|
6
|
+
Verge::Server::User.all.destroy!
|
7
|
+
@user = Factory(:user)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "doesn't allow duplicate logins" do
|
11
|
+
Factory(:user, :login => @user.login).should_not be_valid
|
12
|
+
end
|
13
|
+
|
14
|
+
it "finds user when provided valid credentials" do
|
15
|
+
Verge::Server::User.authenticate(@user.login, "0rbital").should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns nil if no user is found for credentials" do
|
19
|
+
Verge::Server::User.authenticate("nobody", "---").should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates a token with an expiry" do
|
23
|
+
expiry = DateTime.now + 100
|
24
|
+
@user.generate_token(expiry)
|
25
|
+
@user.tokens.first.expiry.should eql(expiry)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "destroys any tokens that belong to it when destroyed" do
|
29
|
+
@user.token
|
30
|
+
@user.destroy
|
31
|
+
Verge::Server::Token.count.should eql(0)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "valid token returns an existing token when one's been created" do
|
35
|
+
@user.generate_token
|
36
|
+
count = @user.tokens.count
|
37
|
+
@user.token
|
38
|
+
|
39
|
+
@user.tokens.count.should eql(count)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "valid token creates a new token when none exist" do
|
43
|
+
count = @user.tokens.count
|
44
|
+
@user.token
|
45
|
+
|
46
|
+
@user.tokens.count.should eql(count + 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "starts out as an inactive user and has an expiry" do
|
50
|
+
@user.activated.should be_false
|
51
|
+
@user.expiry.should >= DateTime.now + 4.minutes
|
52
|
+
end
|
53
|
+
|
54
|
+
it "will mark the model as activated and remove the expiry" do
|
55
|
+
@user.activate!
|
56
|
+
end
|
57
|
+
|
58
|
+
it "removes any uses that have expired" do
|
59
|
+
Factory(:user, :expiry => 1.minute.ago)
|
60
|
+
count = Verge::Server::User.count
|
61
|
+
Verge::Server::User.remove_expired_users
|
62
|
+
Verge::Server::User.count.should == (count - 1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe Verge::Server::SignedToken do
|
67
|
+
before :each do
|
68
|
+
@signed_token = Factory(:signed_token)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "doesn't allow duplicate signatures" do
|
72
|
+
Factory(:signed_token, :value => @signed_token.value).should_not be_valid
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Verge::Server::Token do
|
77
|
+
before :each do
|
78
|
+
Verge::Server::SignedToken.all.destroy!
|
79
|
+
Verge::Server::Token.all.destroy!
|
80
|
+
Factory(:site)
|
81
|
+
@user = Factory(:user)
|
82
|
+
|
83
|
+
@token = Verge::Server::Token.create(:user_id => @user.id)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "has a valid token" do
|
87
|
+
@token.value.length.should eql(128)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "has a user_id" do
|
91
|
+
new_token = Verge::Server::Token.new
|
92
|
+
new_token.user_id.should be_nil
|
93
|
+
new_token.should_not be_valid
|
94
|
+
end
|
95
|
+
|
96
|
+
it "destroys any signed tokens that belong to it when destroyed" do
|
97
|
+
@token.destroy
|
98
|
+
Verge::Server::SignedToken.count.should eql(0)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "automatically creates signed tokens for each site when created" do
|
102
|
+
Verge::Server::Site.count.should eql(Verge::Server::SignedToken.count)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe Verge::Server::Site do
|
107
|
+
before :each do
|
108
|
+
Verge::Server::Site.all.destroy!
|
109
|
+
Verge::Server::User.all.destroy!
|
110
|
+
Verge::Server::Token.all.destroy!
|
111
|
+
Verge::Server::SignedToken.all.destroy!
|
112
|
+
|
113
|
+
@user = Factory(:user)
|
114
|
+
@site = Factory(:site)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "returns valid token after signing token" do
|
118
|
+
@site.sign_token(Verge::Server::User.first.token).value.length.should == 128
|
119
|
+
end
|
120
|
+
|
121
|
+
it "destroys any signed tokens that belong to it when destroyed" do
|
122
|
+
Verge::Server::Token.create(:user_id => @user.id)
|
123
|
+
@site.signed_tokens.destroy
|
124
|
+
Verge::Server::SignedToken.count.should eql(0)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "signs any tokens that haven't expired when created" do
|
128
|
+
@user.token
|
129
|
+
Verge::Server::SignedToken.count.should eql(Verge::Server::Site.count)
|
130
|
+
# Factory(:site) uses create! which will bypass the after hook
|
131
|
+
Factory.build(:site).save.should be_true
|
132
|
+
Verge::Server::SignedToken.count.should eql(Verge::Server::Site.count)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "returns a site when the domain and protocol match" do
|
136
|
+
site1 = Factory(:site)
|
137
|
+
site2 = Verge::Server::Site.find_by_url("http://#{site1.domain}/some/other/path?junk=10&true=false")
|
138
|
+
site2.should_not be_nil
|
139
|
+
site2.domain.should eql(site1.domain)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should create a valid signed token when signing" do
|
143
|
+
@user.token.should_not be_nil
|
144
|
+
Verge::Server::SignedToken.count.should eql(1)
|
145
|
+
signature = Verge::Crypto.digest(@site.signature, @user.login, @user.token.value)
|
146
|
+
Verge::Server::SignedToken.first.value.should == signature
|
147
|
+
end
|
148
|
+
end
|
data/spec/rcov.opts
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'verge'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'rack/test'
|
7
|
+
require 'factory_girl'
|
8
|
+
|
9
|
+
require File.expand_path(File.dirname(__FILE__)) + '/factories'
|
10
|
+
|
11
|
+
Spec::Runner.configure do |config|
|
12
|
+
config.include Rack::Test::Methods
|
13
|
+
|
14
|
+
# Add an app method for RSpec
|
15
|
+
def app
|
16
|
+
Rack::Lint.new(Verge::Server::Base.new)
|
17
|
+
end
|
18
|
+
end
|
data/spec/verge_spec.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
module VergeSpecHelper
|
4
|
+
def valid_auth_request_for_user(user)
|
5
|
+
{:login => user.login, :password => "0rbital"}
|
6
|
+
end
|
7
|
+
|
8
|
+
def new_user_credentials_for_site(site)
|
9
|
+
login = 'new-verge-user'
|
10
|
+
{:login => login, :password => '0rbital', :signature => site.sign(login)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Verge::Server do
|
15
|
+
include VergeSpecHelper
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
@site = Factory(:site)
|
19
|
+
header("Referer", @site.domain)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "GET to /token.js" do
|
23
|
+
it "echos cookie back in javascript" do
|
24
|
+
login = "astro"
|
25
|
+
token = "bombastic"
|
26
|
+
|
27
|
+
set_cookie("login=#{login}")
|
28
|
+
set_cookie("token=#{token}")
|
29
|
+
|
30
|
+
get '/token.js'
|
31
|
+
last_response.body.should =~ /#{login}/
|
32
|
+
last_response.body.should =~ /#{token}/
|
33
|
+
end
|
34
|
+
|
35
|
+
it "echos nothing if no cookies are sent" do
|
36
|
+
get '/token.js'
|
37
|
+
last_response.body.should == ""
|
38
|
+
last_response.should be_ok
|
39
|
+
end
|
40
|
+
|
41
|
+
it "echos nothing if login is blank" do
|
42
|
+
set_cookie("login=")
|
43
|
+
set_cookie("token=a-token")
|
44
|
+
|
45
|
+
get '/token.js'
|
46
|
+
last_response.body.should == ""
|
47
|
+
last_response.should be_ok
|
48
|
+
end
|
49
|
+
|
50
|
+
it "echos nothing if login is blank" do
|
51
|
+
set_cookie("login=a-login")
|
52
|
+
set_cookie("token=")
|
53
|
+
|
54
|
+
get '/token.js'
|
55
|
+
last_response.body.should == ""
|
56
|
+
last_response.should be_ok
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
describe 'GET to /auth' do
|
62
|
+
before :each do
|
63
|
+
@user = Factory(:user)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'fails with empty request' do
|
67
|
+
get '/auth'
|
68
|
+
last_response.status.should == 401
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns a code when valid' do
|
72
|
+
get '/auth', valid_auth_request_for_user(@user)
|
73
|
+
last_response.body.should == @user.token.value
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'sets a cookie on success' do
|
77
|
+
get '/auth', valid_auth_request_for_user(@user)
|
78
|
+
last_response.headers["Set-Cookie"].should == "token=#{@user.token.value}; path=/"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "POST to /create" do
|
83
|
+
before :each do
|
84
|
+
Verge::Server::User.all.destroy!
|
85
|
+
end
|
86
|
+
|
87
|
+
it "fails if site not found" do
|
88
|
+
header("Referer", "BAD://SITE")
|
89
|
+
post '/create'
|
90
|
+
|
91
|
+
last_response.status.should == 401
|
92
|
+
end
|
93
|
+
|
94
|
+
it "creates a new user" do
|
95
|
+
post '/create', new_user_credentials_for_site(@site)
|
96
|
+
last_response.should be_ok
|
97
|
+
last_response.body.should == Verge::Server::User.first.token.value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'GET to /verify/:token' do
|
102
|
+
before :each do
|
103
|
+
@user = Factory(:user)
|
104
|
+
@signed_token = @user.token.signed_tokens.first(:site_id => @site.id)
|
105
|
+
|
106
|
+
header("Referer", @site.domain)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "fails if no regisered site is found" do
|
110
|
+
header("Referer", "BAD://SITE")
|
111
|
+
get "/verify/anything"
|
112
|
+
|
113
|
+
last_response.status.should == 401
|
114
|
+
end
|
115
|
+
|
116
|
+
it "fails if token can't be found" do
|
117
|
+
get "/verify/anything"
|
118
|
+
|
119
|
+
last_response.status.should == 404
|
120
|
+
end
|
121
|
+
|
122
|
+
it "succeeds if the token is valid" do
|
123
|
+
get "/verify/#{@signed_token.value}"
|
124
|
+
|
125
|
+
last_response.should be_ok
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/verge.gemspec
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{verge}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Adam Elliot"]
|
12
|
+
s.date = %q{2009-10-20}
|
13
|
+
s.description = %q{Simple system that grants trusted sites tokens if users have successfully authenticated. So they are free to interact with each other securely.}
|
14
|
+
s.email = %q{adam@wartube.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/verge.rb",
|
27
|
+
"lib/verge/crypto.rb",
|
28
|
+
"lib/verge/server/base.rb",
|
29
|
+
"lib/verge/server/models.rb",
|
30
|
+
"lib/verge/server/views/token.js.erb",
|
31
|
+
"spec/crypto_spec.rb",
|
32
|
+
"spec/factories.rb",
|
33
|
+
"spec/models_spec.rb",
|
34
|
+
"spec/rcov.opts",
|
35
|
+
"spec/spec.opts",
|
36
|
+
"spec/spec_helper.rb",
|
37
|
+
"spec/verge_spec.rb",
|
38
|
+
"verge.gemspec"
|
39
|
+
]
|
40
|
+
s.homepage = %q{http://github.com/adamelliot/verge}
|
41
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
42
|
+
s.require_paths = ["lib"]
|
43
|
+
s.rubygems_version = %q{1.3.5}
|
44
|
+
s.summary = %q{Lightweight centralized authentication system built on Sinatra}
|
45
|
+
s.test_files = [
|
46
|
+
"spec/crypto_spec.rb",
|
47
|
+
"spec/factories.rb",
|
48
|
+
"spec/models_spec.rb",
|
49
|
+
"spec/spec_helper.rb",
|
50
|
+
"spec/verge_spec.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
|
59
|
+
s.add_runtime_dependency(%q<datamapper>, [">= 0"])
|
60
|
+
s.add_runtime_dependency(%q<bcrypt-ruby>, [">= 0"])
|
61
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<factory_girl>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<rack-test>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<do_sqlite3>, [">= 0"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
68
|
+
s.add_dependency(%q<datamapper>, [">= 0"])
|
69
|
+
s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
|
70
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
71
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
72
|
+
s.add_dependency(%q<factory_girl>, [">= 0"])
|
73
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
74
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
78
|
+
s.add_dependency(%q<datamapper>, [">= 0"])
|
79
|
+
s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
|
80
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
81
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
82
|
+
s.add_dependency(%q<factory_girl>, [">= 0"])
|
83
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
84
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0"])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: verge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Elliot
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-20 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: datamapper
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bcrypt-ruby
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: activesupport
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: factory_girl
|
67
|
+
type: :development
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rack-test
|
77
|
+
type: :development
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0"
|
84
|
+
version:
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: do_sqlite3
|
87
|
+
type: :development
|
88
|
+
version_requirement:
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: "0"
|
94
|
+
version:
|
95
|
+
description: Simple system that grants trusted sites tokens if users have successfully authenticated. So they are free to interact with each other securely.
|
96
|
+
email: adam@wartube.com
|
97
|
+
executables: []
|
98
|
+
|
99
|
+
extensions: []
|
100
|
+
|
101
|
+
extra_rdoc_files:
|
102
|
+
- LICENSE
|
103
|
+
- README.rdoc
|
104
|
+
files:
|
105
|
+
- .document
|
106
|
+
- .gitignore
|
107
|
+
- LICENSE
|
108
|
+
- README.rdoc
|
109
|
+
- Rakefile
|
110
|
+
- VERSION
|
111
|
+
- lib/verge.rb
|
112
|
+
- lib/verge/crypto.rb
|
113
|
+
- lib/verge/server/base.rb
|
114
|
+
- lib/verge/server/models.rb
|
115
|
+
- lib/verge/server/views/token.js.erb
|
116
|
+
- spec/crypto_spec.rb
|
117
|
+
- spec/factories.rb
|
118
|
+
- spec/models_spec.rb
|
119
|
+
- spec/rcov.opts
|
120
|
+
- spec/spec.opts
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/verge_spec.rb
|
123
|
+
- verge.gemspec
|
124
|
+
has_rdoc: true
|
125
|
+
homepage: http://github.com/adamelliot/verge
|
126
|
+
licenses: []
|
127
|
+
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options:
|
130
|
+
- --charset=UTF-8
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: "0"
|
138
|
+
version:
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: "0"
|
144
|
+
version:
|
145
|
+
requirements: []
|
146
|
+
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 1.3.5
|
149
|
+
signing_key:
|
150
|
+
specification_version: 3
|
151
|
+
summary: Lightweight centralized authentication system built on Sinatra
|
152
|
+
test_files:
|
153
|
+
- spec/crypto_spec.rb
|
154
|
+
- spec/factories.rb
|
155
|
+
- spec/models_spec.rb
|
156
|
+
- spec/spec_helper.rb
|
157
|
+
- spec/verge_spec.rb
|