omniauth 0.2.6 → 0.3.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of omniauth might be problematic. Click here for more details.
- data/README.md +62 -34
- data/lib/omniauth/version.rb +3 -3
- metadata +74 -228
- data/Gemfile +0 -12
- data/Rakefile +0 -51
- data/oa-basic/Gemfile +0 -7
- data/oa-basic/LICENSE +0 -19
- data/oa-basic/README.rdoc +0 -34
- data/oa-basic/Rakefile +0 -6
- data/oa-basic/lib/oa-basic.rb +0 -1
- data/oa-basic/lib/omniauth/basic.rb +0 -8
- data/oa-basic/lib/omniauth/strategies/http_basic.rb +0 -56
- data/oa-basic/lib/omniauth/version.rb +0 -19
- data/oa-basic/oa-basic.gemspec +0 -27
- data/oa-basic/spec/omniauth/strategies/basic_spec.rb +0 -7
- data/oa-basic/spec/spec_helper.rb +0 -11
- data/oa-core/Gemfile +0 -3
- data/oa-core/LICENSE +0 -19
- data/oa-core/Rakefile +0 -6
- data/oa-core/autotest/discover.rb +0 -1
- data/oa-core/lib/oa-core.rb +0 -1
- data/oa-core/lib/omniauth/builder.rb +0 -33
- data/oa-core/lib/omniauth/core.rb +0 -135
- data/oa-core/lib/omniauth/form.rb +0 -186
- data/oa-core/lib/omniauth/strategy.rb +0 -227
- data/oa-core/lib/omniauth/test.rb +0 -12
- data/oa-core/lib/omniauth/test/phony_session.rb +0 -8
- data/oa-core/lib/omniauth/test/strategy_macros.rb +0 -34
- data/oa-core/lib/omniauth/test/strategy_test_case.rb +0 -49
- data/oa-core/lib/omniauth/version.rb +0 -19
- data/oa-core/oa-core.gemspec +0 -24
- data/oa-core/spec/omniauth/builder_spec.rb +0 -20
- data/oa-core/spec/omniauth/core_spec.rb +0 -79
- data/oa-core/spec/omniauth/strategy_spec.rb +0 -363
- data/oa-core/spec/spec_helper.rb +0 -12
- data/oa-enterprise/Gemfile +0 -7
- data/oa-enterprise/LICENSE +0 -19
- data/oa-enterprise/README.rdoc +0 -82
- data/oa-enterprise/Rakefile +0 -6
- data/oa-enterprise/lib/oa-enterprise.rb +0 -1
- data/oa-enterprise/lib/omniauth/enterprise.rb +0 -8
- data/oa-enterprise/lib/omniauth/strategies/cas.rb +0 -47
- data/oa-enterprise/lib/omniauth/strategies/cas/configuration.rb +0 -98
- data/oa-enterprise/lib/omniauth/strategies/cas/service_ticket_validator.rb +0 -91
- data/oa-enterprise/lib/omniauth/strategies/ldap.rb +0 -111
- data/oa-enterprise/lib/omniauth/strategies/ldap/adaptor.rb +0 -279
- data/oa-enterprise/lib/omniauth/version.rb +0 -19
- data/oa-enterprise/oa-enterprise.gemspec +0 -31
- data/oa-enterprise/spec/fixtures/cas_failure.xml +0 -4
- data/oa-enterprise/spec/fixtures/cas_success.xml +0 -8
- data/oa-enterprise/spec/omniauth/strategies/cas_spec.rb +0 -94
- data/oa-enterprise/spec/omniauth/strategies/ldap_spec.rb +0 -41
- data/oa-enterprise/spec/spec_helper.rb +0 -14
- data/oa-more/Gemfile +0 -7
- data/oa-more/LICENSE +0 -19
- data/oa-more/README.rdoc +0 -22
- data/oa-more/Rakefile +0 -6
- data/oa-more/lib/oa-more.rb +0 -1
- data/oa-more/lib/omniauth/more.rb +0 -11
- data/oa-more/lib/omniauth/strategies/draugiem.rb +0 -104
- data/oa-more/lib/omniauth/strategies/flickr.rb +0 -86
- data/oa-more/lib/omniauth/strategies/ign.rb +0 -93
- data/oa-more/lib/omniauth/strategies/windows_live.rb +0 -39
- data/oa-more/lib/omniauth/strategies/windows_live/windowslivelogin.rb +0 -1143
- data/oa-more/lib/omniauth/strategies/yupoo.rb +0 -67
- data/oa-more/lib/omniauth/version.rb +0 -19
- data/oa-more/oa-more.gemspec +0 -29
- data/oa-more/spec/omniauth/strategies/draugiem_spec.rb +0 -51
- data/oa-more/spec/omniauth/strategies/flickr_spec.rb +0 -7
- data/oa-more/spec/spec_helper.rb +0 -11
- data/oa-oauth/Gemfile +0 -7
- data/oa-oauth/LICENSE +0 -19
- data/oa-oauth/README.rdoc +0 -35
- data/oa-oauth/Rakefile +0 -6
- data/oa-oauth/autotest/discover.rb +0 -1
- data/oa-oauth/lib/oa-oauth.rb +0 -1
- data/oa-oauth/lib/omniauth/oauth.rb +0 -56
- data/oa-oauth/lib/omniauth/strategies/bitly.rb +0 -46
- data/oa-oauth/lib/omniauth/strategies/dailymile.rb +0 -64
- data/oa-oauth/lib/omniauth/strategies/doit.rb +0 -60
- data/oa-oauth/lib/omniauth/strategies/dopplr.rb +0 -53
- data/oa-oauth/lib/omniauth/strategies/douban.rb +0 -60
- data/oa-oauth/lib/omniauth/strategies/evernote.rb +0 -54
- data/oa-oauth/lib/omniauth/strategies/facebook.rb +0 -70
- data/oa-oauth/lib/omniauth/strategies/foursquare.rb +0 -62
- data/oa-oauth/lib/omniauth/strategies/github.rb +0 -50
- data/oa-oauth/lib/omniauth/strategies/goodreads.rb +0 -44
- data/oa-oauth/lib/omniauth/strategies/google.rb +0 -80
- data/oa-oauth/lib/omniauth/strategies/gowalla.rb +0 -72
- data/oa-oauth/lib/omniauth/strategies/hyves.rb +0 -67
- data/oa-oauth/lib/omniauth/strategies/identica.rb +0 -49
- data/oa-oauth/lib/omniauth/strategies/instagram.rb +0 -56
- data/oa-oauth/lib/omniauth/strategies/instapaper.rb +0 -40
- data/oa-oauth/lib/omniauth/strategies/linked_in.rb +0 -56
- data/oa-oauth/lib/omniauth/strategies/mailru.rb +0 -107
- data/oa-oauth/lib/omniauth/strategies/meetup.rb +0 -56
- data/oa-oauth/lib/omniauth/strategies/miso.rb +0 -41
- data/oa-oauth/lib/omniauth/strategies/mixi.rb +0 -59
- data/oa-oauth/lib/omniauth/strategies/netflix.rb +0 -65
- data/oa-oauth/lib/omniauth/strategies/oauth.rb +0 -83
- data/oa-oauth/lib/omniauth/strategies/oauth2.rb +0 -91
- data/oa-oauth/lib/omniauth/strategies/plurk.rb +0 -58
- data/oa-oauth/lib/omniauth/strategies/qzone.rb +0 -69
- data/oa-oauth/lib/omniauth/strategies/rdio.rb +0 -45
- data/oa-oauth/lib/omniauth/strategies/renren.rb +0 -87
- data/oa-oauth/lib/omniauth/strategies/salesforce.rb +0 -44
- data/oa-oauth/lib/omniauth/strategies/smug_mug.rb +0 -42
- data/oa-oauth/lib/omniauth/strategies/sound_cloud.rb +0 -46
- data/oa-oauth/lib/omniauth/strategies/t163.rb +0 -57
- data/oa-oauth/lib/omniauth/strategies/taobao.rb +0 -79
- data/oa-oauth/lib/omniauth/strategies/teambox.rb +0 -49
- data/oa-oauth/lib/omniauth/strategies/thirty_seven_signals.rb +0 -41
- data/oa-oauth/lib/omniauth/strategies/tqq.rb +0 -64
- data/oa-oauth/lib/omniauth/strategies/trade_me.rb +0 -45
- data/oa-oauth/lib/omniauth/strategies/trip_it.rb +0 -22
- data/oa-oauth/lib/omniauth/strategies/tsina.rb +0 -79
- data/oa-oauth/lib/omniauth/strategies/tsohu.rb +0 -57
- data/oa-oauth/lib/omniauth/strategies/tumblr.rb +0 -60
- data/oa-oauth/lib/omniauth/strategies/twitter.rb +0 -57
- data/oa-oauth/lib/omniauth/strategies/type_pad.rb +0 -76
- data/oa-oauth/lib/omniauth/strategies/vimeo.rb +0 -54
- data/oa-oauth/lib/omniauth/strategies/vkontakte.rb +0 -87
- data/oa-oauth/lib/omniauth/strategies/xauth.rb +0 -67
- data/oa-oauth/lib/omniauth/strategies/yahoo.rb +0 -55
- data/oa-oauth/lib/omniauth/strategies/yammer.rb +0 -43
- data/oa-oauth/lib/omniauth/strategies/you_tube.rb +0 -73
- data/oa-oauth/lib/omniauth/version.rb +0 -19
- data/oa-oauth/oa-oauth.gemspec +0 -32
- data/oa-oauth/spec/fixtures/basecamp_200.xml +0 -24
- data/oa-oauth/spec/fixtures/campfire_200.json +0 -10
- data/oa-oauth/spec/omniauth/strategies/bitly_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/dailymile_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/doit_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/dopplr_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/douban_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/evernote_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/facebook_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/foursquare_spec.rb +0 -18
- data/oa-oauth/spec/omniauth/strategies/github_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/goodreads_spec.rb +0 -6
- data/oa-oauth/spec/omniauth/strategies/google_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/gowalla_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/hyves_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/identica_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/linked_in_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/mailru_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/meetup_spec.rb +0 -14
- data/oa-oauth/spec/omniauth/strategies/miso_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/netflix_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/oauth2_spec.rb +0 -0
- data/oa-oauth/spec/omniauth/strategies/oauth_spec.rb +0 -77
- data/oa-oauth/spec/omniauth/strategies/plurk_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/rdio_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/salesforce_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/smug_mug_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/sound_cloud_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/t163_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/taobao_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/teambox_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/thirty_seven_signals_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/trade_me_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/trip_it_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/tsina_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/tumblr_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/twitter_spec.rb +0 -20
- data/oa-oauth/spec/omniauth/strategies/type_pad_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/vimeo_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/vkontakte_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/yahoo_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/yammer_spec.rb +0 -5
- data/oa-oauth/spec/omniauth/strategies/you_tube_spec.rb +0 -5
- data/oa-oauth/spec/spec_helper.rb +0 -27
- data/oa-oauth/spec/support/shared_examples.rb +0 -29
- data/oa-openid/Gemfile +0 -7
- data/oa-openid/LICENSE +0 -19
- data/oa-openid/README.rdoc +0 -51
- data/oa-openid/Rakefile +0 -6
- data/oa-openid/lib/oa-openid.rb +0 -1
- data/oa-openid/lib/omniauth/openid.rb +0 -60
- data/oa-openid/lib/omniauth/openid/gapps.rb +0 -32
- data/oa-openid/lib/omniauth/strategies/google_apps.rb +0 -23
- data/oa-openid/lib/omniauth/strategies/open_id.rb +0 -132
- data/oa-openid/lib/omniauth/strategies/steam.rb +0 -55
- data/oa-openid/lib/omniauth/version.rb +0 -19
- data/oa-openid/oa-openid.gemspec +0 -28
- data/oa-openid/spec/omniauth/strategies/open_id_spec.rb +0 -71
- data/oa-openid/spec/spec_helper.rb +0 -14
- data/omniauth.gemspec +0 -20
- data/tasks/all.rb +0 -134
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'simplecov'
|
2
|
-
SimpleCov.start
|
3
|
-
require 'rspec'
|
4
|
-
require 'rack/test'
|
5
|
-
require 'webmock/rspec'
|
6
|
-
require 'omniauth/core'
|
7
|
-
require 'omniauth/test'
|
8
|
-
require 'omniauth/enterprise'
|
9
|
-
|
10
|
-
RSpec.configure do |config|
|
11
|
-
config.include WebMock::API
|
12
|
-
config.include Rack::Test::Methods
|
13
|
-
config.extend OmniAuth::Test::StrategyMacros, :type => :strategy
|
14
|
-
end
|
data/oa-more/Gemfile
DELETED
data/oa-more/LICENSE
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
Copyright (c) 2010-2011 Michael Bleigh and Intridea, Inc.
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
of this software and associated documentation files (the "Software"), to deal
|
5
|
-
in the Software without restriction, including without limitation the rights
|
6
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
copies of the Software, and to permit persons to whom the Software is
|
8
|
-
furnished to do so, subject to the following conditions:
|
9
|
-
|
10
|
-
The above copyright notice and this permission notice shall be included in
|
11
|
-
all copies or substantial portions of the Software.
|
12
|
-
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
THE SOFTWARE.
|
data/oa-more/README.rdoc
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
= OmniAuth::More
|
2
|
-
|
3
|
-
OmniAuth stratgies for authentication providers that do not
|
4
|
-
fit into one of the other authentication gems.
|
5
|
-
|
6
|
-
== Installation
|
7
|
-
|
8
|
-
To install omniauth as a suite of gems:
|
9
|
-
|
10
|
-
gem install omniauth
|
11
|
-
|
12
|
-
To install just the providers in the "more" gem:
|
13
|
-
|
14
|
-
gem install oa-more
|
15
|
-
|
16
|
-
== OmniAuth Builder
|
17
|
-
|
18
|
-
If you want to allow multiple providers, use the OmniAuth Builder:
|
19
|
-
|
20
|
-
use OmniAuth::Builder do
|
21
|
-
provider :flickr, 'api_key', 'secret_key', :scope => 'read'
|
22
|
-
end
|
data/oa-more/Rakefile
DELETED
data/oa-more/lib/oa-more.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'omniauth/more'
|
@@ -1,11 +0,0 @@
|
|
1
|
-
require 'omniauth/core'
|
2
|
-
|
3
|
-
module OmniAuth
|
4
|
-
module Strategies
|
5
|
-
autoload :WindowsLive, 'omniauth/strategies/windows_live'
|
6
|
-
autoload :Flickr, 'omniauth/strategies/flickr'
|
7
|
-
autoload :Yupoo, 'omniauth/strategies/yupoo'
|
8
|
-
autoload :Ign, 'omniauth/strategies/ign'
|
9
|
-
autoload :Draugiem, 'omniauth/strategies/draugiem'
|
10
|
-
end
|
11
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
require 'omniauth/core'
|
2
|
-
require 'digest/md5'
|
3
|
-
require 'rest-client'
|
4
|
-
require 'multi_json'
|
5
|
-
|
6
|
-
module OmniAuth
|
7
|
-
module Strategies
|
8
|
-
#
|
9
|
-
# Authenticate to draugiem.lv and frype.com and others.
|
10
|
-
#
|
11
|
-
# @example Basic Rails Usage
|
12
|
-
#
|
13
|
-
# Add this to config/initializers/omniauth.rb
|
14
|
-
#
|
15
|
-
# Rails.application.config.middleware.use OmniAuth::Builder do
|
16
|
-
# provider :draugiem, 'App id', 'API Key'
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# @example Basic Rack example
|
20
|
-
#
|
21
|
-
# use Rack::Session::Cookie
|
22
|
-
# use OmniAuth::Strategies::Draugiem, 'App id', 'API Key'
|
23
|
-
#
|
24
|
-
class Draugiem
|
25
|
-
include OmniAuth::Strategy
|
26
|
-
attr_accessor :app_id, :api_key
|
27
|
-
|
28
|
-
def initialize(app, app_id, api_key)
|
29
|
-
super(app, :draugiem)
|
30
|
-
@app_id = app_id
|
31
|
-
@api_key = api_key
|
32
|
-
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
|
36
|
-
def request_phase
|
37
|
-
params = {
|
38
|
-
:app => @app_id,
|
39
|
-
:redirect => callback_url,
|
40
|
-
:hash => Digest::MD5.hexdigest("#{@api_key}#{callback_url}")
|
41
|
-
}
|
42
|
-
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
43
|
-
redirect "http://api.draugiem.lv/authorize/?#{query_string}"
|
44
|
-
end
|
45
|
-
|
46
|
-
def callback_phase
|
47
|
-
if request.params['dr_auth_status'] == 'ok' && request.params['dr_auth_code']
|
48
|
-
response = RestClient.get('http://api.draugiem.lv/json/', { :params => draugiem_authorize_params(request.params['dr_auth_code']) })
|
49
|
-
auth = MultiJson.decode(response.to_s)
|
50
|
-
unless auth['error']
|
51
|
-
@auth_data = auth
|
52
|
-
super
|
53
|
-
else
|
54
|
-
fail!(auth['error']['code'].to_s,auth["error"]["description"].to_s)
|
55
|
-
end
|
56
|
-
else
|
57
|
-
fail!(:invalid_request)
|
58
|
-
end
|
59
|
-
rescue Exception => e
|
60
|
-
fail!(:invalid_response, e)
|
61
|
-
end
|
62
|
-
|
63
|
-
def auth_hash
|
64
|
-
OmniAuth::Utils.deep_merge(super, {
|
65
|
-
'uid' => @auth_data['uid'],
|
66
|
-
'user_info' => get_user_info,
|
67
|
-
'credentials' => {
|
68
|
-
'apikey' => @auth_data['apikey']
|
69
|
-
},
|
70
|
-
'extra' => { 'user_hash' => @auth_data }
|
71
|
-
})
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def get_user_info
|
77
|
-
if @auth_data['users'] && @auth_data['users'][@auth_data['uid']]
|
78
|
-
user = @auth_data['users'][@auth_data['uid']]
|
79
|
-
{
|
80
|
-
'name' => "#{user['name']} #{user['surname']}",
|
81
|
-
'nickname' => user['nick'],
|
82
|
-
'first_name' => user['name'],
|
83
|
-
'last_name' => user['surname'],
|
84
|
-
'location' => user['place'],
|
85
|
-
'age' => user['age'] =~ /^0-9$/ ? user['age'] : nil,
|
86
|
-
'adult' => user['adult'] == '1' ? true : false,
|
87
|
-
'image' => user['img'],
|
88
|
-
'sex' => user['sex']
|
89
|
-
}
|
90
|
-
else
|
91
|
-
{}
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def draugiem_authorize_params code
|
96
|
-
{
|
97
|
-
:action => 'authorize',
|
98
|
-
:app => @api_key,
|
99
|
-
:code => code
|
100
|
-
}
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'omniauth/core'
|
2
|
-
require 'digest/md5'
|
3
|
-
require 'rest-client'
|
4
|
-
require 'multi_json'
|
5
|
-
|
6
|
-
module OmniAuth
|
7
|
-
module Strategies
|
8
|
-
#
|
9
|
-
# Authenticate to Flickr
|
10
|
-
#
|
11
|
-
# @example Basic Usage
|
12
|
-
#
|
13
|
-
# use OmniAuth::Strategies::Flickr, 'API Key', 'Secret Key', :scope => 'read'
|
14
|
-
class Flickr
|
15
|
-
include OmniAuth::Strategy
|
16
|
-
attr_accessor :api_key, :secret_key, :options
|
17
|
-
|
18
|
-
# error catching, based on OAuth2 callback
|
19
|
-
class CallbackError < StandardError
|
20
|
-
attr_accessor :error, :error_reason
|
21
|
-
def initialize(error, error_reason)
|
22
|
-
self.error = error
|
23
|
-
self.error_reason = error_reason
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# @param [Rack Application] app standard middleware application parameter
|
28
|
-
# @param [String] api_key the application id as [registered on Flickr](http://www.flickr.com/services/apps/)
|
29
|
-
# @param [String] secret_key the application secret as [registered on Flickr](http://www.flickr.com/services/apps/)
|
30
|
-
# @option options ['read','write','delete] :scope ('read') the scope of your authorization request; must be `read` or 'write' or 'delete'
|
31
|
-
def initialize(app, api_key, secret_key, options = {})
|
32
|
-
super(app, :flickr)
|
33
|
-
@api_key = api_key
|
34
|
-
@secret_key = secret_key
|
35
|
-
@options = {:scope => 'read'}.merge(options)
|
36
|
-
end
|
37
|
-
|
38
|
-
protected
|
39
|
-
|
40
|
-
def request_phase
|
41
|
-
params = { :api_key => api_key, :perms => options[:scope] }
|
42
|
-
params[:api_sig] = flickr_sign(params)
|
43
|
-
query_string = params.collect{ |key,value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
44
|
-
redirect "http://flickr.com/services/auth/?#{query_string}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def callback_phase
|
48
|
-
params = { :api_key => api_key, :method => 'flickr.auth.getToken', :frob => request.params['frob'], :format => 'json', :nojsoncallback => '1' }
|
49
|
-
params[:api_sig] = flickr_sign(params)
|
50
|
-
|
51
|
-
response = RestClient.get('http://api.flickr.com/services/rest/', { :params => params })
|
52
|
-
auth = MultiJson.decode(response.to_s)
|
53
|
-
raise CallbackError.new(auth['code'],auth['message']) if auth['stat'] == 'fail'
|
54
|
-
|
55
|
-
@user = auth['auth']['user']
|
56
|
-
@access_token = auth['auth']['token']['_content']
|
57
|
-
|
58
|
-
super
|
59
|
-
rescue CallbackError => e
|
60
|
-
fail!(:invalid_response, e)
|
61
|
-
end
|
62
|
-
|
63
|
-
def auth_hash
|
64
|
-
OmniAuth::Utils.deep_merge(super, {
|
65
|
-
'uid' => @user['nsid'],
|
66
|
-
'credentials' => { 'token' => @access_token },
|
67
|
-
'user_info' => user_info,
|
68
|
-
'extra' => { 'user_hash' => @user }
|
69
|
-
})
|
70
|
-
end
|
71
|
-
|
72
|
-
def user_info
|
73
|
-
name = @user['fullname']
|
74
|
-
name = @user['username'] if name.nil? || name.empty?
|
75
|
-
{
|
76
|
-
'nickname' => @user['username'],
|
77
|
-
'name' => name,
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
def flickr_sign(params)
|
82
|
-
Digest::MD5.hexdigest(secret_key + params.sort{|a,b| a[0].to_s <=> b[0].to_s }.flatten.join)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
require 'omniauth/core'
|
2
|
-
require 'openssl'
|
3
|
-
|
4
|
-
module OmniAuth
|
5
|
-
module Strategies
|
6
|
-
class Ign
|
7
|
-
include OmniAuth::Strategy
|
8
|
-
IDENTIFIER_URL_PARAMETER = ""
|
9
|
-
|
10
|
-
class CallbackError < StandardError
|
11
|
-
attr_accessor :error, :error_reason
|
12
|
-
def initialize(error, error_reason)
|
13
|
-
self.error = error
|
14
|
-
self.error_reason = error_reason
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(app, api_key, hostname=nil, options = {})
|
19
|
-
options[:name] ||= "ign"
|
20
|
-
super(app, :ign)
|
21
|
-
@api_key = api_key
|
22
|
-
@hostname = hostname
|
23
|
-
end
|
24
|
-
|
25
|
-
protected
|
26
|
-
|
27
|
-
def request_phase
|
28
|
-
OmniAuth::Form.build(:title => 'IGN Authentication', :header_info=>js) do
|
29
|
-
label_field('Identifying you with the IGN server', IDENTIFIER_URL_PARAMETER)
|
30
|
-
end.to_response
|
31
|
-
end
|
32
|
-
|
33
|
-
def callback_phase
|
34
|
-
signature = OpenSSL::HMAC.hexdigest('sha1', @api_key, ("#{request.params["username"]}::#{request.params["timestamp"]}"))
|
35
|
-
|
36
|
-
raise CallbackError.new("Invalid Signature","The supplied and calculated signature did not match, user not approved.") if signature != request.params["signature"]
|
37
|
-
|
38
|
-
super
|
39
|
-
rescue CallbackError => e
|
40
|
-
fail!(:invalid_response, e)
|
41
|
-
end
|
42
|
-
|
43
|
-
def auth_hash
|
44
|
-
OmniAuth::Utils.deep_merge(super, {
|
45
|
-
'uid' => "ign-" + request.params["username"],
|
46
|
-
'credentials' => { 'token' => request.params["signature"] },
|
47
|
-
'user_info' => user_info,
|
48
|
-
'extra' => { 'user_hash' => request.params }
|
49
|
-
})
|
50
|
-
end
|
51
|
-
|
52
|
-
def user_info
|
53
|
-
{
|
54
|
-
'nickname' => request.params["username"],
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
def js
|
59
|
-
@js = <<-JS
|
60
|
-
$(document).ready(function() {
|
61
|
-
$.ajax({
|
62
|
-
url: "http://#{@hostname}/users/current.json?callback=z33k",
|
63
|
-
type: "get",
|
64
|
-
dataType:"jsonp",
|
65
|
-
success: function(data) {
|
66
|
-
if(typeof data.error == 'undefined'){
|
67
|
-
// There is a current My IGN user
|
68
|
-
var username = data.my_ign_username;
|
69
|
-
var signature = data.signature;
|
70
|
-
var timestamp = data.timestamp;
|
71
|
-
window.location = "/auth/ign/callback?username=" +username+"&signature="+signature+"×tamp=" + timestamp;
|
72
|
-
}
|
73
|
-
else{
|
74
|
-
nouser();
|
75
|
-
}
|
76
|
-
}
|
77
|
-
});
|
78
|
-
return false;
|
79
|
-
});
|
80
|
-
function nouser() {
|
81
|
-
var url = "http://my.ign.com/login?r="+window.location;
|
82
|
-
top.location = url;
|
83
|
-
window.location = url;
|
84
|
-
}
|
85
|
-
JS
|
86
|
-
"\n<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js' type='text/javascript'></script>" +
|
87
|
-
"\n<script type='text/javascript'>#{@js}</script>" +
|
88
|
-
"\n<style type='text/css'>button {visibility:hidden;}</style>"
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'omniauth/core'
|
2
|
-
require 'omniauth/strategies/windows_live/windowslivelogin'
|
3
|
-
|
4
|
-
module OmniAuth
|
5
|
-
module Strategies
|
6
|
-
class WindowsLive
|
7
|
-
include OmniAuth::Strategy
|
8
|
-
|
9
|
-
attr_accessor :app_id, :app_secret
|
10
|
-
|
11
|
-
# Initialize the strategy by providing
|
12
|
-
#
|
13
|
-
# @param app_id [String] The application ID from your registered app with Microsoft.
|
14
|
-
# @param app_secret [String] The secret from your registered app with Microsoft.
|
15
|
-
# @option options [String] :locale A localization string for the login, should be in the form `en-us` or similar.
|
16
|
-
# @option options [String] :state Some state information that is serialized into the query string upon callback.
|
17
|
-
# @option options [Boolean] :ssl Whether or not to use SSL for login. Defaults to `true`.
|
18
|
-
# @option options [Boolean] :force_nonprovisioned When true, forces a non-provisioned (i.e. no app id or secret) mode.
|
19
|
-
def initialize(app, app_id = nil, app_secret = nil, options = {})
|
20
|
-
self.app_id = app_id
|
21
|
-
self.app_secret = app_secret
|
22
|
-
super(app, :windows_live, app_id, app_secret, options)
|
23
|
-
options[:ssl] ||= true
|
24
|
-
options[:locale] ||= 'en-us'
|
25
|
-
options[:force_nonprovisioned] = true unless app_id
|
26
|
-
end
|
27
|
-
|
28
|
-
protected
|
29
|
-
|
30
|
-
def consumer
|
31
|
-
WindowsLiveLogin.new app_id, app_secret, options[:security_algorithm], options[:force_nonprovisioned], options[:policy_url], callback_url
|
32
|
-
end
|
33
|
-
|
34
|
-
def request_phase
|
35
|
-
redirect consumer.getLoginUrl(options[:state], options[:locale])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,1143 +0,0 @@
|
|
1
|
-
#######################################################################
|
2
|
-
# FILE: windowslivelogin.rb
|
3
|
-
#
|
4
|
-
# DESCRIPTION: Sample implementation of Web Authentication and
|
5
|
-
# Delegated Authentication protocol in Ruby. Also
|
6
|
-
# includes trusted sign-in and application verification
|
7
|
-
# sample implementations.
|
8
|
-
#
|
9
|
-
# VERSION: 1.1
|
10
|
-
#
|
11
|
-
# Copyright (c) 2008 Microsoft Corporation. All Rights Reserved.
|
12
|
-
#######################################################################
|
13
|
-
|
14
|
-
require 'cgi'
|
15
|
-
require 'uri'
|
16
|
-
require 'base64'
|
17
|
-
require 'openssl'
|
18
|
-
require 'net/https'
|
19
|
-
require 'rexml/document'
|
20
|
-
|
21
|
-
module OmniAuth; module Strategies; class WindowsLive; class WindowsLiveLogin
|
22
|
-
|
23
|
-
#####################################################################
|
24
|
-
# Stub implementation for logging errors. If you want to enable
|
25
|
-
# debugging output using the default mechanism, specify true.
|
26
|
-
# By default, debug information will be printed to the standard
|
27
|
-
# error output and should be visible in the web server logs.
|
28
|
-
#####################################################################
|
29
|
-
def setDebug(flag)
|
30
|
-
@debug = flag
|
31
|
-
end
|
32
|
-
|
33
|
-
#####################################################################
|
34
|
-
# Stub implementation for logging errors. By default, this function
|
35
|
-
# does nothing if the debug flag has not been set with setDebug.
|
36
|
-
# Otherwise, it tries to log the error message.
|
37
|
-
#####################################################################
|
38
|
-
def debug(error)
|
39
|
-
return unless @debug
|
40
|
-
return if error.nil? or error.empty?
|
41
|
-
warn("Windows Live ID Authentication SDK #{error}")
|
42
|
-
nil
|
43
|
-
end
|
44
|
-
|
45
|
-
#####################################################################
|
46
|
-
# Stub implementation for handling a fatal error.
|
47
|
-
#####################################################################
|
48
|
-
def fatal(error)
|
49
|
-
debug(error)
|
50
|
-
raise(error)
|
51
|
-
end
|
52
|
-
|
53
|
-
#####################################################################
|
54
|
-
# Initialize the WindowsLiveLogin module with the application ID,
|
55
|
-
# secret key, and security algorithm.
|
56
|
-
#
|
57
|
-
# We recommend that you employ strong measures to protect the
|
58
|
-
# secret key. The secret key should never be exposed to the Web
|
59
|
-
# or other users.
|
60
|
-
#
|
61
|
-
# Be aware that if you do not supply these settings at
|
62
|
-
# initialization time, you may need to set the corresponding
|
63
|
-
# properties manually.
|
64
|
-
#
|
65
|
-
# For Delegated Authentication, you may optionally specify the
|
66
|
-
# privacy policy URL and return URL. If you do not specify these
|
67
|
-
# values here, the default values that you specified when you
|
68
|
-
# registered your application will be used.
|
69
|
-
#
|
70
|
-
# The 'force_delauth_nonprovisioned' flag also indicates whether
|
71
|
-
# your application is registered for Delegated Authentication
|
72
|
-
# (that is, whether it uses an application ID and secret key). We
|
73
|
-
# recommend that your Delegated Authentication application always
|
74
|
-
# be registered for enhanced security and functionality.
|
75
|
-
#####################################################################
|
76
|
-
def initialize(appid=nil, secret=nil, securityalgorithm=nil,
|
77
|
-
force_delauth_nonprovisioned=nil,
|
78
|
-
policyurl=nil, returnurl=nil)
|
79
|
-
self.force_delauth_nonprovisioned = force_delauth_nonprovisioned
|
80
|
-
self.appid = appid if appid
|
81
|
-
self.secret = secret if secret
|
82
|
-
self.securityalgorithm = securityalgorithm if securityalgorithm
|
83
|
-
self.policyurl = policyurl if policyurl
|
84
|
-
self.returnurl = returnurl if returnurl
|
85
|
-
end
|
86
|
-
|
87
|
-
#####################################################################
|
88
|
-
# Initialize the WindowsLiveLogin module from a settings file.
|
89
|
-
#
|
90
|
-
# 'settingsFile' specifies the location of the XML settings file
|
91
|
-
# that contains the application ID, secret key, and security
|
92
|
-
# algorithm. The file is of the following format:
|
93
|
-
#
|
94
|
-
# <windowslivelogin>
|
95
|
-
# <appid>APPID</appid>
|
96
|
-
# <secret>SECRET</secret>
|
97
|
-
# <securityalgorithm>wsignin1.0</securityalgorithm>
|
98
|
-
# </windowslivelogin>
|
99
|
-
#
|
100
|
-
# In a Delegated Authentication scenario, you may also specify
|
101
|
-
# 'returnurl' and 'policyurl' in the settings file, as shown in the
|
102
|
-
# Delegated Authentication samples.
|
103
|
-
#
|
104
|
-
# We recommend that you store the WindowsLiveLogin settings file
|
105
|
-
# in an area on your server that cannot be accessed through the
|
106
|
-
# Internet. This file contains important confidential information.
|
107
|
-
#####################################################################
|
108
|
-
def self.initFromXml(settingsFile)
|
109
|
-
o = self.new
|
110
|
-
settings = o.parseSettings(settingsFile)
|
111
|
-
|
112
|
-
o.setDebug(settings['debug'] == 'true')
|
113
|
-
o.force_delauth_nonprovisioned =
|
114
|
-
(settings['force_delauth_nonprovisioned'] == 'true')
|
115
|
-
|
116
|
-
o.appid = settings['appid']
|
117
|
-
o.secret = settings['secret']
|
118
|
-
o.oldsecret = settings['oldsecret']
|
119
|
-
o.oldsecretexpiry = settings['oldsecretexpiry']
|
120
|
-
o.securityalgorithm = settings['securityalgorithm']
|
121
|
-
o.policyurl = settings['policyurl']
|
122
|
-
o.returnurl = settings['returnurl']
|
123
|
-
o.baseurl = settings['baseurl']
|
124
|
-
o.secureurl = settings['secureurl']
|
125
|
-
o.consenturl = settings['consenturl']
|
126
|
-
o
|
127
|
-
end
|
128
|
-
|
129
|
-
#####################################################################
|
130
|
-
# Sets the application ID. Use this method if you did not specify
|
131
|
-
# an application ID at initialization.
|
132
|
-
#####################################################################
|
133
|
-
def appid=(appid)
|
134
|
-
if (appid.nil? or appid.empty?)
|
135
|
-
return if force_delauth_nonprovisioned
|
136
|
-
fatal("Error: appid: Null application ID.")
|
137
|
-
end
|
138
|
-
if (not appid =~ /^\w+$/)
|
139
|
-
fatal("Error: appid: Application ID must be alpha-numeric: " + appid)
|
140
|
-
end
|
141
|
-
@appid = appid
|
142
|
-
end
|
143
|
-
|
144
|
-
#####################################################################
|
145
|
-
# Returns the application ID.
|
146
|
-
#####################################################################
|
147
|
-
def appid
|
148
|
-
if (@appid.nil? or @appid.empty?)
|
149
|
-
fatal("Error: appid: App ID was not set. Aborting.")
|
150
|
-
end
|
151
|
-
@appid
|
152
|
-
end
|
153
|
-
|
154
|
-
#####################################################################
|
155
|
-
# Sets your secret key. Use this method if you did not specify
|
156
|
-
# a secret key at initialization.
|
157
|
-
#####################################################################
|
158
|
-
def secret=(secret)
|
159
|
-
if (secret.nil? or secret.empty?)
|
160
|
-
return if force_delauth_nonprovisioned
|
161
|
-
fatal("Error: secret=: Secret must be non-null.")
|
162
|
-
end
|
163
|
-
if (secret.size < 16)
|
164
|
-
fatal("Error: secret=: Secret must be at least 16 characters.")
|
165
|
-
end
|
166
|
-
@signkey = derive(secret, "SIGNATURE")
|
167
|
-
@cryptkey = derive(secret, "ENCRYPTION")
|
168
|
-
end
|
169
|
-
|
170
|
-
#####################################################################
|
171
|
-
# Sets your old secret key.
|
172
|
-
#
|
173
|
-
# Use this property to set your old secret key if you are in the
|
174
|
-
# process of transitioning to a new secret key. You may need this
|
175
|
-
# property because the Windows Live ID servers can take up to
|
176
|
-
# 24 hours to propagate a new secret key after you have updated
|
177
|
-
# your application settings.
|
178
|
-
#
|
179
|
-
# If an old secret key is specified here and has not expired
|
180
|
-
# (as determined by the oldsecretexpiry setting), it will be used
|
181
|
-
# as a fallback if token decryption fails with the new secret
|
182
|
-
# key.
|
183
|
-
#####################################################################
|
184
|
-
def oldsecret=(secret)
|
185
|
-
return if (secret.nil? or secret.empty?)
|
186
|
-
if (secret.size < 16)
|
187
|
-
fatal("Error: oldsecret=: Secret must be at least 16 characters.")
|
188
|
-
end
|
189
|
-
@oldsignkey = derive(secret, "SIGNATURE")
|
190
|
-
@oldcryptkey = derive(secret, "ENCRYPTION")
|
191
|
-
end
|
192
|
-
|
193
|
-
#####################################################################
|
194
|
-
# Sets the expiry time for your old secret key.
|
195
|
-
#
|
196
|
-
# After this time has passed, the old secret key will no longer be
|
197
|
-
# used even if token decryption fails with the new secret key.
|
198
|
-
#
|
199
|
-
# The old secret expiry time is represented as the number of seconds
|
200
|
-
# elapsed since January 1, 1970.
|
201
|
-
#####################################################################
|
202
|
-
def oldsecretexpiry=(timestamp)
|
203
|
-
return if (timestamp.nil? or timestamp.empty?)
|
204
|
-
timestamp = timestamp.to_i
|
205
|
-
fatal("Error: oldsecretexpiry=: Invalid timestamp: #{timestamp}") if (timestamp <= 0)
|
206
|
-
@oldsecretexpiry = Time.at timestamp
|
207
|
-
end
|
208
|
-
|
209
|
-
#####################################################################
|
210
|
-
# Gets the old secret key expiry time.
|
211
|
-
#####################################################################
|
212
|
-
attr_accessor :oldsecretexpiry
|
213
|
-
|
214
|
-
#####################################################################
|
215
|
-
# Sets or gets the version of the security algorithm being used.
|
216
|
-
#####################################################################
|
217
|
-
attr_accessor :securityalgorithm
|
218
|
-
|
219
|
-
def securityalgorithm
|
220
|
-
if(@securityalgorithm.nil? or @securityalgorithm.empty?)
|
221
|
-
"wsignin1.0"
|
222
|
-
else
|
223
|
-
@securityalgorithm
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
#####################################################################
|
228
|
-
# Sets a flag that indicates whether Delegated Authentication
|
229
|
-
# is non-provisioned (i.e. does not use an application ID or secret
|
230
|
-
# key).
|
231
|
-
#####################################################################
|
232
|
-
attr_accessor :force_delauth_nonprovisioned
|
233
|
-
|
234
|
-
#####################################################################
|
235
|
-
# Sets the privacy policy URL, to which the Windows Live ID consent
|
236
|
-
# service redirects users to view the privacy policy of your Web
|
237
|
-
# site for Delegated Authentication.
|
238
|
-
#####################################################################
|
239
|
-
def policyurl=(policyurl)
|
240
|
-
if ((policyurl.nil? or policyurl.empty?) and force_delauth_nonprovisioned)
|
241
|
-
fatal("Error: policyurl=: Invalid policy URL specified.")
|
242
|
-
end
|
243
|
-
@policyurl = policyurl
|
244
|
-
end
|
245
|
-
|
246
|
-
#####################################################################
|
247
|
-
# Gets the privacy policy URL for your site.
|
248
|
-
#####################################################################
|
249
|
-
def policyurl
|
250
|
-
if (@policyurl.nil? or @policyurl.empty?)
|
251
|
-
debug("Warning: In the initial release of Del Auth, a Policy URL must be configured in the SDK for both provisioned and non-provisioned scenarios.")
|
252
|
-
raise("Error: policyurl: Policy URL must be set in a Del Auth non-provisioned scenario. Aborting.") if force_delauth_nonprovisioned
|
253
|
-
end
|
254
|
-
@policyurl
|
255
|
-
end
|
256
|
-
|
257
|
-
#####################################################################
|
258
|
-
# Sets the return URL--the URL on your site to which the consent
|
259
|
-
# service redirects users (along with the action, consent token,
|
260
|
-
# and application context) after they have successfully provided
|
261
|
-
# consent information for Delegated Authentication. This value will
|
262
|
-
# override the return URL specified during registration.
|
263
|
-
#####################################################################
|
264
|
-
def returnurl=(returnurl)
|
265
|
-
if ((returnurl.nil? or returnurl.empty?) and force_delauth_nonprovisioned)
|
266
|
-
fatal("Error: returnurl=: Invalid return URL specified.")
|
267
|
-
end
|
268
|
-
@returnurl = returnurl
|
269
|
-
end
|
270
|
-
|
271
|
-
|
272
|
-
#####################################################################
|
273
|
-
# Returns the return URL of your site.
|
274
|
-
#####################################################################
|
275
|
-
def returnurl
|
276
|
-
if ((@returnurl.nil? or @returnurl.empty?) and force_delauth_nonprovisioned)
|
277
|
-
fatal("Error: returnurl: Return URL must be set in a Del Auth non-provisioned scenario. Aborting.")
|
278
|
-
end
|
279
|
-
@returnurl
|
280
|
-
end
|
281
|
-
|
282
|
-
#####################################################################
|
283
|
-
# Sets or gets the base URL to use for the Windows Live Login server. You
|
284
|
-
# should not have to change this property. Furthermore, we recommend
|
285
|
-
# that you use the Sign In control instead of the URL methods
|
286
|
-
# provided here.
|
287
|
-
#####################################################################
|
288
|
-
attr_accessor :baseurl
|
289
|
-
|
290
|
-
def baseurl
|
291
|
-
if(@baseurl.nil? or @baseurl.empty?)
|
292
|
-
"http://login.live.com/"
|
293
|
-
else
|
294
|
-
@baseurl
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
#####################################################################
|
299
|
-
# Sets or gets the secure (HTTPS) URL to use for the Windows Live Login
|
300
|
-
# server. You should not have to change this property.
|
301
|
-
#####################################################################
|
302
|
-
attr_accessor :secureurl
|
303
|
-
|
304
|
-
def secureurl
|
305
|
-
if(@secureurl.nil? or @secureurl.empty?)
|
306
|
-
"https://login.live.com/"
|
307
|
-
else
|
308
|
-
@secureurl
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
#####################################################################
|
313
|
-
# Sets or gets the Consent Base URL to use for the Windows Live Consent
|
314
|
-
# server. You should not have to use or change this property directly.
|
315
|
-
#####################################################################
|
316
|
-
attr_accessor :consenturl
|
317
|
-
|
318
|
-
def consenturl
|
319
|
-
if(@consenturl.nil? or @consenturl.empty?)
|
320
|
-
"https://consent.live.com/"
|
321
|
-
else
|
322
|
-
@consenturl
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
#######################################################################
|
328
|
-
# Implementation of the basic methods needed for Web Authentication.
|
329
|
-
#######################################################################
|
330
|
-
class WindowsLiveLogin
|
331
|
-
#####################################################################
|
332
|
-
# Returns the sign-in URL to use for the Windows Live Login server.
|
333
|
-
# We recommend that you use the Sign In control instead.
|
334
|
-
#
|
335
|
-
# If you specify it, 'context' will be returned as-is in the sign-in
|
336
|
-
# response for site-specific use.
|
337
|
-
#####################################################################
|
338
|
-
def getLoginUrl(context=nil, market=nil)
|
339
|
-
url = baseurl + "wlogin.srf?appid=#{appid}"
|
340
|
-
url += "&alg=#{securityalgorithm}"
|
341
|
-
url += "&appctx=#{CGI.escape(context)}" if context
|
342
|
-
url += "&mkt=#{CGI.escape(market)}" if market
|
343
|
-
url
|
344
|
-
end
|
345
|
-
|
346
|
-
#####################################################################
|
347
|
-
# Returns the sign-out URL to use for the Windows Live Login server.
|
348
|
-
# We recommend that you use the Sign In control instead.
|
349
|
-
#####################################################################
|
350
|
-
def getLogoutUrl(market=nil)
|
351
|
-
url = baseurl + "logout.srf?appid=#{appid}"
|
352
|
-
url += "&mkt=#{CGI.escape(market)}" if market
|
353
|
-
url
|
354
|
-
end
|
355
|
-
|
356
|
-
#####################################################################
|
357
|
-
# Holds the user information after a successful sign-in.
|
358
|
-
#
|
359
|
-
# 'timestamp' is the time as obtained from the SSO token.
|
360
|
-
# 'id' is the pairwise unique ID for the user.
|
361
|
-
# 'context' is the application context that was originally passed to
|
362
|
-
# the sign-in request, if any.
|
363
|
-
# 'token' is the encrypted Web Authentication token that contains the
|
364
|
-
# UID. This can be cached in a cookie and the UID can be retrieved by
|
365
|
-
# calling the processToken method.
|
366
|
-
# 'usePersistentCookie?' indicates whether the application is
|
367
|
-
# expected to store the user token in a session or persistent
|
368
|
-
# cookie.
|
369
|
-
#####################################################################
|
370
|
-
class User
|
371
|
-
attr_reader :timestamp, :id, :context, :token
|
372
|
-
|
373
|
-
def usePersistentCookie?
|
374
|
-
@usePersistentCookie
|
375
|
-
end
|
376
|
-
|
377
|
-
|
378
|
-
#####################################################################
|
379
|
-
# Initialize the User with time stamp, userid, flags, context and token.
|
380
|
-
#####################################################################
|
381
|
-
def initialize(timestamp, id, flags, context, token)
|
382
|
-
self.timestamp = timestamp
|
383
|
-
self.id = id
|
384
|
-
self.flags = flags
|
385
|
-
self.context = context
|
386
|
-
self.token = token
|
387
|
-
end
|
388
|
-
|
389
|
-
private
|
390
|
-
attr_writer :timestamp, :id, :flags, :context, :token
|
391
|
-
|
392
|
-
#####################################################################
|
393
|
-
# Sets or gets the Unix timestamp as obtained from the SSO token.
|
394
|
-
#####################################################################
|
395
|
-
def timestamp=(timestamp)
|
396
|
-
raise("Error: User: Null timestamp in token.") unless timestamp
|
397
|
-
timestamp = timestamp.to_i
|
398
|
-
raise("Error: User: Invalid timestamp: #{timestamp}") if (timestamp <= 0)
|
399
|
-
@timestamp = Time.at timestamp
|
400
|
-
end
|
401
|
-
|
402
|
-
#####################################################################
|
403
|
-
# Sets or gets the pairwise unique ID for the user.
|
404
|
-
#####################################################################
|
405
|
-
def id=(id)
|
406
|
-
raise("Error: User: Null id in token.") unless id
|
407
|
-
raise("Error: User: Invalid id: #{id}") unless (id =~ /^\w+$/)
|
408
|
-
@id = id
|
409
|
-
end
|
410
|
-
|
411
|
-
#####################################################################
|
412
|
-
# Sets or gets the usePersistentCookie flag for the user.
|
413
|
-
#####################################################################
|
414
|
-
def flags=(flags)
|
415
|
-
@usePersistentCookie = false
|
416
|
-
if flags
|
417
|
-
@usePersistentCookie = ((flags.to_i % 2) == 1)
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
#####################################################################
|
423
|
-
# Processes the sign-in response from the Windows Live sign-in server.
|
424
|
-
#
|
425
|
-
# 'query' contains the preprocessed POST table, such as that
|
426
|
-
# returned by CGI.params or Rails. (The unprocessed POST string
|
427
|
-
# could also be used here but we do not recommend it).
|
428
|
-
#
|
429
|
-
# This method returns a User object on successful sign-in; otherwise
|
430
|
-
# it returns nil.
|
431
|
-
#####################################################################
|
432
|
-
def processLogin(query)
|
433
|
-
query = parse query
|
434
|
-
unless query
|
435
|
-
debug("Error: processLogin: Failed to parse query.")
|
436
|
-
return
|
437
|
-
end
|
438
|
-
action = query['action']
|
439
|
-
unless action == 'login'
|
440
|
-
debug("Warning: processLogin: query action ignored: #{action}.")
|
441
|
-
return
|
442
|
-
end
|
443
|
-
token = query['stoken']
|
444
|
-
context = CGI.unescape(query['appctx']) if query['appctx']
|
445
|
-
processToken(token, context)
|
446
|
-
end
|
447
|
-
|
448
|
-
#####################################################################
|
449
|
-
# Decodes and validates a Web Authentication token. Returns a User
|
450
|
-
# object on success. If a context is passed in, it will be returned
|
451
|
-
# as the context field in the User object.
|
452
|
-
#####################################################################
|
453
|
-
def processToken(token, context=nil)
|
454
|
-
if token.nil? or token.empty?
|
455
|
-
debug("Error: processToken: Null/empty token.")
|
456
|
-
return
|
457
|
-
end
|
458
|
-
stoken = decodeAndValidateToken token
|
459
|
-
stoken = parse stoken
|
460
|
-
unless stoken
|
461
|
-
debug("Error: processToken: Failed to decode/validate token: #{token}")
|
462
|
-
return
|
463
|
-
end
|
464
|
-
sappid = stoken['appid']
|
465
|
-
unless sappid == appid
|
466
|
-
debug("Error: processToken: Application ID in token did not match ours: #{sappid}, #{appid}")
|
467
|
-
return
|
468
|
-
end
|
469
|
-
begin
|
470
|
-
user = User.new(stoken['ts'], stoken['uid'], stoken['flags'],
|
471
|
-
context, token)
|
472
|
-
return user
|
473
|
-
rescue Exception => e
|
474
|
-
debug("Error: processToken: Contents of token considered invalid: #{e}")
|
475
|
-
return
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
#####################################################################
|
480
|
-
# Returns an appropriate content type and body response that the
|
481
|
-
# application handler can return to signify a successful sign-out
|
482
|
-
# from the application.
|
483
|
-
#
|
484
|
-
# When a user signs out of Windows Live or a Windows Live
|
485
|
-
# application, a best-effort attempt is made at signing the user out
|
486
|
-
# from all other Windows Live applications the user might be signed
|
487
|
-
# in to. This is done by calling the handler page for each
|
488
|
-
# application with 'action' set to 'clearcookie' in the query
|
489
|
-
# string. The application handler is then responsible for clearing
|
490
|
-
# any cookies or data associated with the sign-in. After successfully
|
491
|
-
# signing the user out, the handler should return a GIF (any GIF)
|
492
|
-
# image as response to the 'action=clearcookie' query.
|
493
|
-
#####################################################################
|
494
|
-
def getClearCookieResponse()
|
495
|
-
type = "image/gif"
|
496
|
-
content = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7"
|
497
|
-
content = Base64.decode64(content)
|
498
|
-
return type, content
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
#######################################################################
|
503
|
-
# Implementation of the basic methods needed for Delegated
|
504
|
-
# Authentication.
|
505
|
-
#######################################################################
|
506
|
-
class WindowsLiveLogin
|
507
|
-
#####################################################################
|
508
|
-
# Returns the consent URL to use for Delegated Authentication for
|
509
|
-
# the given comma-delimited list of offers.
|
510
|
-
#
|
511
|
-
# If you specify it, 'context' will be returned as-is in the consent
|
512
|
-
# response for site-specific use.
|
513
|
-
#
|
514
|
-
# The registered/configured return URL can also be overridden by
|
515
|
-
# specifying 'ru' here.
|
516
|
-
#
|
517
|
-
# You can change the language in which the consent page is displayed
|
518
|
-
# by specifying a culture ID (For example, 'fr-fr' or 'en-us') in the
|
519
|
-
# 'market' parameter.
|
520
|
-
#####################################################################
|
521
|
-
def getConsentUrl(offers, context=nil, ru=nil, market=nil)
|
522
|
-
if (offers.nil? or offers.empty?)
|
523
|
-
fatal("Error: getConsentUrl: Invalid offers list.")
|
524
|
-
end
|
525
|
-
url = consenturl + "Delegation.aspx?ps=#{CGI.escape(offers)}"
|
526
|
-
url += "&appctx=#{CGI.escape(context)}" if context
|
527
|
-
ru = returnurl if (ru.nil? or ru.empty?)
|
528
|
-
url += "&ru=#{CGI.escape(ru)}" if ru
|
529
|
-
pu = policyurl
|
530
|
-
url += "&pl=#{CGI.escape(pu)}" if pu
|
531
|
-
url += "&mkt=#{CGI.escape(market)}" if market
|
532
|
-
url += "&app=#{getAppVerifier()}" unless force_delauth_nonprovisioned
|
533
|
-
url
|
534
|
-
end
|
535
|
-
|
536
|
-
#####################################################################
|
537
|
-
# Returns the URL to use to download a new consent token, given the
|
538
|
-
# offers and refresh token.
|
539
|
-
# The registered/configured return URL can also be overridden by
|
540
|
-
# specifying 'ru' here.
|
541
|
-
#####################################################################
|
542
|
-
def getRefreshConsentTokenUrl(offers, refreshtoken, ru)
|
543
|
-
if (offers.nil? or offers.empty?)
|
544
|
-
fatal("Error: getRefreshConsentTokenUrl: Invalid offers list.")
|
545
|
-
end
|
546
|
-
if (refreshtoken.nil? or refreshtoken.empty?)
|
547
|
-
fatal("Error: getRefreshConsentTokenUrl: Invalid refresh token.")
|
548
|
-
end
|
549
|
-
url = consenturl + "RefreshToken.aspx?ps=#{CGI.escape(offers)}"
|
550
|
-
url += "&reft=#{refreshtoken}"
|
551
|
-
ru = returnurl if (ru.nil? or ru.empty?)
|
552
|
-
url += "&ru=#{CGI.escape(ru)}" if ru
|
553
|
-
url += "&app=#{getAppVerifier()}" unless force_delauth_nonprovisioned
|
554
|
-
url
|
555
|
-
end
|
556
|
-
|
557
|
-
#####################################################################
|
558
|
-
# Returns the URL for the consent-management user interface.
|
559
|
-
# You can change the language in which the consent page is displayed
|
560
|
-
# by specifying a culture ID (For example, 'fr-fr' or 'en-us') in the
|
561
|
-
# 'market' parameter.
|
562
|
-
#####################################################################
|
563
|
-
def getManageConsentUrl(market=nil)
|
564
|
-
url = consenturl + "ManageConsent.aspx"
|
565
|
-
url += "?mkt=#{CGI.escape(market)}" if market
|
566
|
-
url
|
567
|
-
end
|
568
|
-
|
569
|
-
class ConsentToken
|
570
|
-
attr_reader :delegationtoken, :refreshtoken, :sessionkey, :expiry
|
571
|
-
attr_reader :offers, :offers_string, :locationid, :context
|
572
|
-
attr_reader :decodedtoken, :token
|
573
|
-
|
574
|
-
#####################################################################
|
575
|
-
# Indicates whether the delegation token is set and has not expired.
|
576
|
-
#####################################################################
|
577
|
-
def isValid?
|
578
|
-
return false unless delegationtoken
|
579
|
-
return ((Time.now.to_i-300) < expiry.to_i)
|
580
|
-
end
|
581
|
-
|
582
|
-
#####################################################################
|
583
|
-
# Refreshes the current token and replace it. If operation succeeds
|
584
|
-
# true is returned to signify success.
|
585
|
-
#####################################################################
|
586
|
-
def refresh
|
587
|
-
ct = @wll.refreshConsentToken(self)
|
588
|
-
return false unless ct
|
589
|
-
copy(ct)
|
590
|
-
true
|
591
|
-
end
|
592
|
-
|
593
|
-
#####################################################################
|
594
|
-
# Initialize the ConsentToken module with the WindowsLiveLogin,
|
595
|
-
# delegation token, refresh token, session key, expiry, offers,
|
596
|
-
# location ID, context, decoded token, and raw token.
|
597
|
-
#####################################################################
|
598
|
-
def initialize(wll, delegationtoken, refreshtoken, sessionkey, expiry,
|
599
|
-
offers, locationid, context, decodedtoken, token)
|
600
|
-
@wll = wll
|
601
|
-
self.delegationtoken = delegationtoken
|
602
|
-
self.refreshtoken = refreshtoken
|
603
|
-
self.sessionkey = sessionkey
|
604
|
-
self.expiry = expiry
|
605
|
-
self.offers = offers
|
606
|
-
self.locationid = locationid
|
607
|
-
self.context = context
|
608
|
-
self.decodedtoken = decodedtoken
|
609
|
-
self.token = token
|
610
|
-
end
|
611
|
-
|
612
|
-
private
|
613
|
-
attr_writer :delegationtoken, :refreshtoken, :sessionkey, :expiry
|
614
|
-
attr_writer :offers, :offers_string, :locationid, :context
|
615
|
-
attr_writer :decodedtoken, :token, :locationid
|
616
|
-
|
617
|
-
#####################################################################
|
618
|
-
# Sets the delegation token.
|
619
|
-
#####################################################################
|
620
|
-
def delegationtoken=(delegationtoken)
|
621
|
-
if (delegationtoken.nil? or delegationtoken.empty?)
|
622
|
-
raise("Error: ConsentToken: Null delegation token.")
|
623
|
-
end
|
624
|
-
@delegationtoken = delegationtoken
|
625
|
-
end
|
626
|
-
|
627
|
-
#####################################################################
|
628
|
-
# Sets the session key.
|
629
|
-
#####################################################################
|
630
|
-
def sessionkey=(sessionkey)
|
631
|
-
if (sessionkey.nil? or sessionkey.empty?)
|
632
|
-
raise("Error: ConsentToken: Null session key.")
|
633
|
-
end
|
634
|
-
@sessionkey = @wll.u64(sessionkey)
|
635
|
-
end
|
636
|
-
|
637
|
-
#####################################################################
|
638
|
-
# Sets the expiry time of the delegation token.
|
639
|
-
#####################################################################
|
640
|
-
def expiry=(expiry)
|
641
|
-
if (expiry.nil? or expiry.empty?)
|
642
|
-
raise("Error: ConsentToken: Null expiry time.")
|
643
|
-
end
|
644
|
-
expiry = expiry.to_i
|
645
|
-
raise("Error: ConsentToken: Invalid expiry: #{expiry}") if (expiry <= 0)
|
646
|
-
@expiry = Time.at expiry
|
647
|
-
end
|
648
|
-
|
649
|
-
#####################################################################
|
650
|
-
# Sets the offers/actions for which the user granted consent.
|
651
|
-
#####################################################################
|
652
|
-
def offers=(offers)
|
653
|
-
if (offers.nil? or offers.empty?)
|
654
|
-
raise("Error: ConsentToken: Null offers.")
|
655
|
-
end
|
656
|
-
|
657
|
-
@offers_string = ""
|
658
|
-
@offers = []
|
659
|
-
|
660
|
-
offers = CGI.unescape(offers)
|
661
|
-
offers = offers.split(";")
|
662
|
-
offers.each{|offer|
|
663
|
-
offer = offer.split(":")[0]
|
664
|
-
@offers_string += "," unless @offers_string.empty?
|
665
|
-
@offers_string += offer
|
666
|
-
@offers.push(offer)
|
667
|
-
}
|
668
|
-
end
|
669
|
-
|
670
|
-
#####################################################################
|
671
|
-
# Sets the LocationID.
|
672
|
-
#####################################################################
|
673
|
-
def locationid=(locationid)
|
674
|
-
if (locationid.nil? or locationid.empty?)
|
675
|
-
raise("Error: ConsentToken: Null Location ID.")
|
676
|
-
end
|
677
|
-
@locationid = locationid
|
678
|
-
end
|
679
|
-
|
680
|
-
#####################################################################
|
681
|
-
# Makes a copy of the ConsentToken object.
|
682
|
-
#####################################################################
|
683
|
-
def copy(consenttoken)
|
684
|
-
@delegationtoken = consenttoken.delegationtoken
|
685
|
-
@refreshtoken = consenttoken.refreshtoken
|
686
|
-
@sessionkey = consenttoken.sessionkey
|
687
|
-
@expiry = consenttoken.expiry
|
688
|
-
@offers = consenttoken.offers
|
689
|
-
@locationid = consenttoken.locationid
|
690
|
-
@offers_string = consenttoken.offers_string
|
691
|
-
@decodedtoken = consenttoken.decodedtoken
|
692
|
-
@token = consenttoken.token
|
693
|
-
end
|
694
|
-
end
|
695
|
-
|
696
|
-
#####################################################################
|
697
|
-
# Processes the POST response from the Delegated Authentication
|
698
|
-
# service after a user has granted consent. The processConsent
|
699
|
-
# function extracts the consent token string and returns the result
|
700
|
-
# of invoking the processConsentToken method.
|
701
|
-
#####################################################################
|
702
|
-
def processConsent(query)
|
703
|
-
query = parse query
|
704
|
-
unless query
|
705
|
-
debug("Error: processConsent: Failed to parse query.")
|
706
|
-
return
|
707
|
-
end
|
708
|
-
action = query['action']
|
709
|
-
unless action == 'delauth'
|
710
|
-
debug("Warning: processConsent: query action ignored: #{action}.")
|
711
|
-
return
|
712
|
-
end
|
713
|
-
responsecode = query['ResponseCode']
|
714
|
-
unless responsecode == 'RequestApproved'
|
715
|
-
debug("Error: processConsent: Consent was not successfully granted: #{responsecode}")
|
716
|
-
return
|
717
|
-
end
|
718
|
-
token = query['ConsentToken']
|
719
|
-
context = CGI.unescape(query['appctx']) if query['appctx']
|
720
|
-
processConsentToken(token, context)
|
721
|
-
end
|
722
|
-
|
723
|
-
#####################################################################
|
724
|
-
# Processes the consent token string that is returned in the POST
|
725
|
-
# response by the Delegated Authentication service after a
|
726
|
-
# user has granted consent.
|
727
|
-
#####################################################################
|
728
|
-
def processConsentToken(token, context=nil)
|
729
|
-
if token.nil? or token.empty?
|
730
|
-
debug("Error: processConsentToken: Null token.")
|
731
|
-
return
|
732
|
-
end
|
733
|
-
decodedtoken = token
|
734
|
-
parsedtoken = parse(CGI.unescape(decodedtoken))
|
735
|
-
unless parsedtoken
|
736
|
-
debug("Error: processConsentToken: Failed to parse token: #{token}")
|
737
|
-
return
|
738
|
-
end
|
739
|
-
eact = parsedtoken['eact']
|
740
|
-
if eact
|
741
|
-
decodedtoken = decodeAndValidateToken eact
|
742
|
-
unless decodedtoken
|
743
|
-
debug("Error: processConsentToken: Failed to decode/validate token: #{token}")
|
744
|
-
return
|
745
|
-
end
|
746
|
-
parsedtoken = parse(decodedtoken)
|
747
|
-
decodedtoken = CGI.escape(decodedtoken)
|
748
|
-
end
|
749
|
-
begin
|
750
|
-
consenttoken = ConsentToken.new(self,
|
751
|
-
parsedtoken['delt'],
|
752
|
-
parsedtoken['reft'],
|
753
|
-
parsedtoken['skey'],
|
754
|
-
parsedtoken['exp'],
|
755
|
-
parsedtoken['offer'],
|
756
|
-
parsedtoken['lid'],
|
757
|
-
context, decodedtoken, token)
|
758
|
-
return consenttoken
|
759
|
-
rescue Exception => e
|
760
|
-
debug("Error: processConsentToken: Contents of token considered invalid: #{e}")
|
761
|
-
return
|
762
|
-
end
|
763
|
-
end
|
764
|
-
|
765
|
-
#####################################################################
|
766
|
-
# Attempts to obtain a new, refreshed token and return it. The
|
767
|
-
# original token is not modified.
|
768
|
-
#####################################################################
|
769
|
-
def refreshConsentToken(consenttoken, ru=nil)
|
770
|
-
if consenttoken.nil?
|
771
|
-
debug("Error: refreshConsentToken: Null consent token.")
|
772
|
-
return
|
773
|
-
end
|
774
|
-
refreshConsentToken2(consenttoken.offers_string, consenttoken.refreshtoken, ru)
|
775
|
-
end
|
776
|
-
|
777
|
-
#####################################################################
|
778
|
-
# Helper function to obtain a new, refreshed token and return it.
|
779
|
-
# The original token is not modified.
|
780
|
-
#####################################################################
|
781
|
-
def refreshConsentToken2(offers_string, refreshtoken, ru=nil)
|
782
|
-
url = nil
|
783
|
-
begin
|
784
|
-
url = getRefreshConsentTokenUrl(offers_string, refreshtoken, ru)
|
785
|
-
ret = fetch url
|
786
|
-
ret.value # raises exception if fetch failed
|
787
|
-
body = ret.body
|
788
|
-
body.scan(/\{"ConsentToken":"(.*)"\}/){|match|
|
789
|
-
return processConsentToken("#{match}")
|
790
|
-
}
|
791
|
-
debug("Error: refreshConsentToken2: Failed to extract token: #{body}")
|
792
|
-
rescue Exception => e
|
793
|
-
debug("Error: Failed to refresh consent token: #{e}")
|
794
|
-
end
|
795
|
-
return
|
796
|
-
end
|
797
|
-
end
|
798
|
-
|
799
|
-
#######################################################################
|
800
|
-
# Common methods.
|
801
|
-
#######################################################################
|
802
|
-
class WindowsLiveLogin
|
803
|
-
|
804
|
-
#####################################################################
|
805
|
-
# Decodes and validates the token.
|
806
|
-
#####################################################################
|
807
|
-
def decodeAndValidateToken(token, cryptkey=@cryptkey, signkey=@signkey,
|
808
|
-
internal_allow_recursion=true)
|
809
|
-
haveoldsecret = false
|
810
|
-
if (oldsecretexpiry and (Time.now.to_i < oldsecretexpiry.to_i))
|
811
|
-
haveoldsecret = true if (@oldcryptkey and @oldsignkey)
|
812
|
-
end
|
813
|
-
haveoldsecret = (haveoldsecret and internal_allow_recursion)
|
814
|
-
|
815
|
-
stoken = decodeToken(token, cryptkey)
|
816
|
-
stoken = validateToken(stoken, signkey) if stoken
|
817
|
-
if (stoken.nil? and haveoldsecret)
|
818
|
-
debug("Warning: Failed to validate token with current secret, attempting old secret.")
|
819
|
-
stoken = decodeAndValidateToken(token, @oldcryptkey, @oldsignkey, false)
|
820
|
-
end
|
821
|
-
stoken
|
822
|
-
end
|
823
|
-
|
824
|
-
#####################################################################
|
825
|
-
# Decodes the given token string; returns undef on failure.
|
826
|
-
#
|
827
|
-
# First, the string is URL-unescaped and base64 decoded.
|
828
|
-
# Second, the IV is extracted from the first 16 bytes of the string.
|
829
|
-
# Finally, the string is decrypted using the encryption key.
|
830
|
-
#####################################################################
|
831
|
-
def decodeToken(token, cryptkey=@cryptkey)
|
832
|
-
if (cryptkey.nil? or cryptkey.empty?)
|
833
|
-
fatal("Error: decodeToken: Secret key was not set. Aborting.")
|
834
|
-
end
|
835
|
-
token = u64(token)
|
836
|
-
if (token.nil? or (token.size <= 16) or !(token.size % 16).zero?)
|
837
|
-
debug("Error: decodeToken: Attempted to decode invalid token.")
|
838
|
-
return
|
839
|
-
end
|
840
|
-
iv = token[0..15]
|
841
|
-
crypted = token[16..-1]
|
842
|
-
begin
|
843
|
-
aes128cbc = OpenSSL::Cipher::AES128.new("CBC")
|
844
|
-
aes128cbc.decrypt
|
845
|
-
aes128cbc.iv = iv
|
846
|
-
aes128cbc.key = cryptkey
|
847
|
-
decrypted = aes128cbc.update(crypted) + aes128cbc.final
|
848
|
-
rescue Exception => e
|
849
|
-
debug("Error: decodeToken: Decryption failed: #{token}, #{e}")
|
850
|
-
return
|
851
|
-
end
|
852
|
-
decrypted
|
853
|
-
end
|
854
|
-
|
855
|
-
#####################################################################
|
856
|
-
# Creates a signature for the given string by using the signature
|
857
|
-
# key.
|
858
|
-
#####################################################################
|
859
|
-
def signToken(token, signkey=@signkey)
|
860
|
-
if (signkey.nil? or signkey.empty?)
|
861
|
-
fatal("Error: signToken: Secret key was not set. Aborting.")
|
862
|
-
end
|
863
|
-
begin
|
864
|
-
digest = OpenSSL::Digest::SHA256.new
|
865
|
-
return OpenSSL::HMAC.digest(digest, signkey, token)
|
866
|
-
rescue Exception => e
|
867
|
-
debug("Error: signToken: Signing failed: #{token}, #{e}")
|
868
|
-
return
|
869
|
-
end
|
870
|
-
end
|
871
|
-
|
872
|
-
#####################################################################
|
873
|
-
# Extracts the signature from the token and validates it.
|
874
|
-
#####################################################################
|
875
|
-
def validateToken(token, signkey=@signkey)
|
876
|
-
if (token.nil? or token.empty?)
|
877
|
-
debug("Error: validateToken: Null token.")
|
878
|
-
return
|
879
|
-
end
|
880
|
-
body, sig = token.split("&sig=")
|
881
|
-
if (body.nil? or sig.nil?)
|
882
|
-
debug("Error: validateToken: Invalid token: #{token}")
|
883
|
-
return
|
884
|
-
end
|
885
|
-
sig = u64(sig)
|
886
|
-
return token if (sig == signToken(body, signkey))
|
887
|
-
debug("Error: validateToken: Signature did not match.")
|
888
|
-
return
|
889
|
-
end
|
890
|
-
end
|
891
|
-
|
892
|
-
#######################################################################
|
893
|
-
# Implementation of the methods needed to perform Windows Live
|
894
|
-
# application verification as well as trusted sign-in.
|
895
|
-
#######################################################################
|
896
|
-
class WindowsLiveLogin
|
897
|
-
#####################################################################
|
898
|
-
# Generates an application verifier token. An IP address can
|
899
|
-
# optionally be included in the token.
|
900
|
-
#####################################################################
|
901
|
-
def getAppVerifier(ip=nil)
|
902
|
-
token = "appid=#{appid}&ts=#{timestamp}"
|
903
|
-
token += "&ip=#{ip}" if ip
|
904
|
-
token += "&sig=#{e64(signToken(token))}"
|
905
|
-
CGI.escape token
|
906
|
-
end
|
907
|
-
|
908
|
-
#####################################################################
|
909
|
-
# Returns the URL that is required to retrieve the application
|
910
|
-
# security token.
|
911
|
-
#
|
912
|
-
# By default, the application security token is generated for
|
913
|
-
# the Windows Live site; a specific Site ID can optionally be
|
914
|
-
# specified in 'siteid'. The IP address can also optionally be
|
915
|
-
# included in 'ip'.
|
916
|
-
#
|
917
|
-
# If 'js' is nil, a JavaScript Output Notation (JSON) response is
|
918
|
-
# returned in the following format:
|
919
|
-
#
|
920
|
-
# {"token":"<value>"}
|
921
|
-
#
|
922
|
-
# Otherwise, a JavaScript response is returned. It is assumed that
|
923
|
-
# WLIDResultCallback is a custom function implemented to handle the
|
924
|
-
# token value:
|
925
|
-
#
|
926
|
-
# WLIDResultCallback("<tokenvalue>");
|
927
|
-
#####################################################################
|
928
|
-
def getAppLoginUrl(siteid=nil, ip=nil, js=nil)
|
929
|
-
url = secureurl + "wapplogin.srf?app=#{getAppVerifier(ip)}"
|
930
|
-
url += "&alg=#{securityalgorithm}"
|
931
|
-
url += "&id=#{siteid}" if siteid
|
932
|
-
url += "&js=1" if js
|
933
|
-
url
|
934
|
-
end
|
935
|
-
|
936
|
-
#####################################################################
|
937
|
-
# Retrieves the application security token for application
|
938
|
-
# verification from the application sign-in URL.
|
939
|
-
#
|
940
|
-
# By default, the application security token will be generated for
|
941
|
-
# the Windows Live site; a specific Site ID can optionally be
|
942
|
-
# specified in 'siteid'. The IP address can also optionally be
|
943
|
-
# included in 'ip'.
|
944
|
-
#
|
945
|
-
# Implementation note: The application security token is downloaded
|
946
|
-
# from the application sign-in URL in JSON format:
|
947
|
-
#
|
948
|
-
# {"token":"<value>"}
|
949
|
-
#
|
950
|
-
# Therefore we must extract <value> from the string and return it as
|
951
|
-
# seen here.
|
952
|
-
#####################################################################
|
953
|
-
def getAppSecurityToken(siteid=nil, ip=nil)
|
954
|
-
url = getAppLoginUrl(siteid, ip)
|
955
|
-
begin
|
956
|
-
ret = fetch url
|
957
|
-
ret.value # raises exception if fetch failed
|
958
|
-
body = ret.body
|
959
|
-
body.scan(/\{"token":"(.*)"\}/){|match|
|
960
|
-
return match
|
961
|
-
}
|
962
|
-
debug("Error: getAppSecurityToken: Failed to extract token: #{body}")
|
963
|
-
rescue Exception => e
|
964
|
-
debug("Error: getAppSecurityToken: Failed to get token: #{e}")
|
965
|
-
end
|
966
|
-
return
|
967
|
-
end
|
968
|
-
|
969
|
-
#####################################################################
|
970
|
-
# Returns a string that can be passed to the getTrustedParams
|
971
|
-
# function as the 'retcode' parameter. If this is specified as the
|
972
|
-
# 'retcode', the application will be used as return URL after it
|
973
|
-
# finishes trusted sign-in.
|
974
|
-
#####################################################################
|
975
|
-
def getAppRetCode
|
976
|
-
"appid=#{appid}"
|
977
|
-
end
|
978
|
-
|
979
|
-
#####################################################################
|
980
|
-
# Returns a table of key-value pairs that must be posted to the
|
981
|
-
# sign-in URL for trusted sign-in. Use HTTP POST to do this. Be aware
|
982
|
-
# that the values in the table are neither URL nor HTML escaped and
|
983
|
-
# may have to be escaped if you are inserting them in code such as
|
984
|
-
# an HTML form.
|
985
|
-
#
|
986
|
-
# The user to be trusted on the local site is passed in as string
|
987
|
-
# 'user'.
|
988
|
-
#
|
989
|
-
# Optionally, 'retcode' specifies the resource to which successful
|
990
|
-
# sign-in is redirected, such as Windows Live Mail, and is typically
|
991
|
-
# a string in the format 'id=2000'. If you pass in the value from
|
992
|
-
# getAppRetCode instead, sign-in will be redirected to the
|
993
|
-
# application. Otherwise, an HTTP 200 response is returned.
|
994
|
-
#####################################################################
|
995
|
-
def getTrustedParams(user, retcode=nil)
|
996
|
-
token = getTrustedToken(user)
|
997
|
-
return unless token
|
998
|
-
token = %{<wst:RequestSecurityTokenResponse xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust"><wst:RequestedSecurityToken><wsse:BinarySecurityToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">#{token}</wsse:BinarySecurityToken></wst:RequestedSecurityToken><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"><wsa:Address>uri:WindowsLiveID</wsa:Address></wsa:EndpointReference></wsp:AppliesTo></wst:RequestSecurityTokenResponse>}
|
999
|
-
params = {}
|
1000
|
-
params['wa'] = securityalgorithm
|
1001
|
-
params['wresult'] = token
|
1002
|
-
params['wctx'] = retcode if retcode
|
1003
|
-
params
|
1004
|
-
end
|
1005
|
-
|
1006
|
-
#####################################################################
|
1007
|
-
# Returns the trusted sign-in token in the format that is needed by a
|
1008
|
-
# control doing trusted sign-in.
|
1009
|
-
#
|
1010
|
-
# The user to be trusted on the local site is passed in as string
|
1011
|
-
# 'user'.
|
1012
|
-
#####################################################################
|
1013
|
-
def getTrustedToken(user)
|
1014
|
-
if user.nil? or user.empty?
|
1015
|
-
debug('Error: getTrustedToken: Null user specified.')
|
1016
|
-
return
|
1017
|
-
end
|
1018
|
-
token = "appid=#{appid}&uid=#{CGI.escape(user)}&ts=#{timestamp}"
|
1019
|
-
token += "&sig=#{e64(signToken(token))}"
|
1020
|
-
CGI.escape token
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
#####################################################################
|
1024
|
-
# Returns the trusted sign-in URL to use for the Windows Live Login
|
1025
|
-
# server.
|
1026
|
-
#####################################################################
|
1027
|
-
def getTrustedLoginUrl
|
1028
|
-
secureurl + "wlogin.srf"
|
1029
|
-
end
|
1030
|
-
|
1031
|
-
#####################################################################
|
1032
|
-
# Returns the trusted sign-out URL to use for the Windows Live Login
|
1033
|
-
# server.
|
1034
|
-
#####################################################################
|
1035
|
-
def getTrustedLogoutUrl
|
1036
|
-
secureurl + "logout.srf?appid=#{appid}"
|
1037
|
-
end
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
#######################################################################
|
1041
|
-
# Helper methods.
|
1042
|
-
#######################################################################
|
1043
|
-
class WindowsLiveLogin
|
1044
|
-
|
1045
|
-
#######################################################################
|
1046
|
-
# Function to parse the settings file.
|
1047
|
-
#######################################################################
|
1048
|
-
def parseSettings(settingsFile)
|
1049
|
-
settings = {}
|
1050
|
-
begin
|
1051
|
-
file = File.new(settingsFile)
|
1052
|
-
doc = REXML::Document.new file
|
1053
|
-
root = doc.root
|
1054
|
-
root.each_element{|e|
|
1055
|
-
settings[e.name] = e.text
|
1056
|
-
}
|
1057
|
-
rescue Exception => e
|
1058
|
-
fatal("Error: parseSettings: Error while reading #{settingsFile}: #{e}")
|
1059
|
-
end
|
1060
|
-
return settings
|
1061
|
-
end
|
1062
|
-
|
1063
|
-
#####################################################################
|
1064
|
-
# Derives the key, given the secret key and prefix as described in the
|
1065
|
-
# Web Authentication SDK documentation.
|
1066
|
-
#####################################################################
|
1067
|
-
def derive(secret, prefix)
|
1068
|
-
begin
|
1069
|
-
fatal("Nil/empty secret.") if (secret.nil? or secret.empty?)
|
1070
|
-
key = prefix + secret
|
1071
|
-
key = OpenSSL::Digest::SHA256.digest(key)
|
1072
|
-
return key[0..15]
|
1073
|
-
rescue Exception => e
|
1074
|
-
debug("Error: derive: #{e}")
|
1075
|
-
return
|
1076
|
-
end
|
1077
|
-
end
|
1078
|
-
|
1079
|
-
#####################################################################
|
1080
|
-
# Parses query string and return a table
|
1081
|
-
# {String=>String}
|
1082
|
-
#
|
1083
|
-
# If a table is passed in from CGI.params, we convert it from
|
1084
|
-
# {String=>[]} to {String=>String}. I believe Rails uses symbols
|
1085
|
-
# instead of strings in general, so we convert from symbols to
|
1086
|
-
# strings here also.
|
1087
|
-
#####################################################################
|
1088
|
-
def parse(input)
|
1089
|
-
if (input.nil? or input.empty?)
|
1090
|
-
debug("Error: parse: Nil/empty input.")
|
1091
|
-
return
|
1092
|
-
end
|
1093
|
-
|
1094
|
-
pairs = {}
|
1095
|
-
if (input.class == String)
|
1096
|
-
input = input.split('&')
|
1097
|
-
input.each{|pair|
|
1098
|
-
k, v = pair.split('=')
|
1099
|
-
pairs[k] = v
|
1100
|
-
}
|
1101
|
-
else
|
1102
|
-
input.each{|k, v|
|
1103
|
-
v = v[0] if (v.class == Array)
|
1104
|
-
pairs[k.to_s] = v.to_s
|
1105
|
-
}
|
1106
|
-
end
|
1107
|
-
return pairs
|
1108
|
-
end
|
1109
|
-
|
1110
|
-
#####################################################################
|
1111
|
-
# Generates a time stamp suitable for the application verifier token.
|
1112
|
-
#####################################################################
|
1113
|
-
def timestamp
|
1114
|
-
Time.now.to_i.to_s
|
1115
|
-
end
|
1116
|
-
|
1117
|
-
#####################################################################
|
1118
|
-
# Base64-encodes and URL-escapes a string.
|
1119
|
-
#####################################################################
|
1120
|
-
def e64(s)
|
1121
|
-
return unless s
|
1122
|
-
CGI.escape Base64.encode64(s)
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
#####################################################################
|
1126
|
-
# URL-unescapes and Base64-decodes a string.
|
1127
|
-
#####################################################################
|
1128
|
-
def u64(s)
|
1129
|
-
return unless s
|
1130
|
-
Base64.decode64 CGI.unescape(s)
|
1131
|
-
end
|
1132
|
-
|
1133
|
-
#####################################################################
|
1134
|
-
# Fetches the contents given a URL.
|
1135
|
-
#####################################################################
|
1136
|
-
def fetch(url)
|
1137
|
-
url = URI.parse url
|
1138
|
-
http = Net::HTTP.new(url.host, url.port)
|
1139
|
-
http.use_ssl = (url.scheme == "https")
|
1140
|
-
http.request_get url.request_uri
|
1141
|
-
end
|
1142
|
-
end end end end
|
1143
|
-
|