rots 1.0.0
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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +3 -0
- data/AUTHORS +4 -0
- data/CHANGELOG.md +22 -0
- data/CONTRIBUTING.md +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +255 -0
- data/SECURITY.md +14 -0
- data/exe/rots +98 -0
- data/lib/rots/identity_page_app.rb +37 -0
- data/lib/rots/mocks/client_app.rb +53 -0
- data/lib/rots/mocks/mock_fetcher.rb +34 -0
- data/lib/rots/mocks/rots_server.rb +49 -0
- data/lib/rots/mocks.rb +8 -0
- data/lib/rots/server_app.rb +167 -0
- data/lib/rots/test/rack_test_helpers.rb +21 -0
- data/lib/rots/test/request_helper.rb +78 -0
- data/lib/rots/test.rb +2 -0
- data/lib/rots/version.rb +5 -0
- data/lib/rots.rb +27 -0
- data.tar.gz.sig +5 -0
- metadata +481 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
# external libraries
|
5
|
+
require "openid"
|
6
|
+
require "openid/extension"
|
7
|
+
require "openid/extensions/sreg"
|
8
|
+
require "openid/store/filesystem"
|
9
|
+
require "openid/util"
|
10
|
+
require "rack/request"
|
11
|
+
require "rack/utils"
|
12
|
+
|
13
|
+
module Rots
|
14
|
+
class ServerApp
|
15
|
+
attr_accessor :request,
|
16
|
+
:openid_request,
|
17
|
+
:response,
|
18
|
+
:openid_response,
|
19
|
+
:server
|
20
|
+
|
21
|
+
def initialize(config, server_options)
|
22
|
+
@server_options = server_options
|
23
|
+
@sreg_fields = config["sreg"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
on_openid_request(env) do
|
28
|
+
if !is_checkid_request?
|
29
|
+
@openid_response = @server.handle_request(@openid_request)
|
30
|
+
reply_consumer
|
31
|
+
elsif is_checkid_immediate?
|
32
|
+
process_immediate_checkid_request
|
33
|
+
else
|
34
|
+
process_checkid_request
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def on_openid_request(env)
|
42
|
+
create_wrappers(env)
|
43
|
+
if @openid_request.nil?
|
44
|
+
[
|
45
|
+
200,
|
46
|
+
{Rack::CONTENT_TYPE => "text/html"},
|
47
|
+
["<html><body><h1>ROTS => This is an OpenID endpoint</h1></body></html>"],
|
48
|
+
]
|
49
|
+
else
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_wrappers(env)
|
55
|
+
@request = Rack::Request.new(env)
|
56
|
+
@server = OpenID::Server::Server.new(storage, op_endpoint)
|
57
|
+
@openid_request = @server.decode_request(@request.params)
|
58
|
+
@openid_sreg_request = OpenID::SReg::Request.from_openid_request(@openid_request) unless @openid_request.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_checkid_request?
|
62
|
+
@openid_request.is_a?(OpenID::Server::CheckIDRequest)
|
63
|
+
end
|
64
|
+
|
65
|
+
def is_checkid_immediate?
|
66
|
+
@openid_request && @openid_request.immediate
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_immediate_checkid_request
|
70
|
+
if checkid_immediate_is_valid?
|
71
|
+
return_successful_openid_response
|
72
|
+
else
|
73
|
+
return_setup_needed_openid_response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def process_checkid_request
|
78
|
+
if checkid_request_is_valid?
|
79
|
+
return_successful_openid_response
|
80
|
+
else
|
81
|
+
return_cancel_openid_response
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def checkid_request_is_valid?
|
86
|
+
@request.params["openid.success"] == "true"
|
87
|
+
end
|
88
|
+
|
89
|
+
def checkid_immediate_is_valid?
|
90
|
+
@request.params["openid.success"] == "true"
|
91
|
+
end
|
92
|
+
|
93
|
+
def return_successful_openid_response
|
94
|
+
@openid_response = @openid_request.answer(true)
|
95
|
+
process_sreg_extension
|
96
|
+
# TODO: Add support for SREG extension
|
97
|
+
@server.signatory.sign(@openid_response) if @openid_response.needs_signing
|
98
|
+
reply_consumer
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_sreg_extension
|
102
|
+
return if @openid_sreg_request.nil?
|
103
|
+
response = OpenID::SReg::Response.extract_response(@openid_sreg_request, @sreg_fields)
|
104
|
+
@openid_response.add_extension(response)
|
105
|
+
end
|
106
|
+
|
107
|
+
def return_cancel_openid_response
|
108
|
+
redirect(@openid_request.cancel_url)
|
109
|
+
end
|
110
|
+
|
111
|
+
def return_setup_needed_openid_response
|
112
|
+
setup_needed_args = @request.params.merge("openid.mode" => "setup_needed", "user_setup_url" => "")
|
113
|
+
url = OpenID::Util.append_args(@openid_request.return_to, setup_needed_args)
|
114
|
+
redirect(url)
|
115
|
+
end
|
116
|
+
|
117
|
+
def reply_consumer
|
118
|
+
web_response = @server.encode_response(@openid_response)
|
119
|
+
case web_response.code
|
120
|
+
when OpenID::Server::HTTP_OK
|
121
|
+
success(web_response.body)
|
122
|
+
when OpenID::Server::HTTP_REDIRECT
|
123
|
+
redirect(web_response.headers["location"])
|
124
|
+
else
|
125
|
+
bad_request
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def redirect(uri)
|
130
|
+
[
|
131
|
+
303,
|
132
|
+
{
|
133
|
+
Rack::CONTENT_LENGTH => "0",
|
134
|
+
Rack::CONTENT_TYPE => "text/plain",
|
135
|
+
"Location" => uri,
|
136
|
+
},
|
137
|
+
[],
|
138
|
+
]
|
139
|
+
end
|
140
|
+
|
141
|
+
def bad_request
|
142
|
+
[
|
143
|
+
400,
|
144
|
+
{Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => "0"},
|
145
|
+
[],
|
146
|
+
]
|
147
|
+
end
|
148
|
+
|
149
|
+
def storage
|
150
|
+
# create the folder if it doesn't exist
|
151
|
+
FileUtils.mkdir_p(@server_options[:storage]) unless File.exist?(@server_options[:storage])
|
152
|
+
OpenID::Store::Filesystem.new(@server_options[:storage])
|
153
|
+
end
|
154
|
+
|
155
|
+
def success(text = "")
|
156
|
+
Rack::Response.new(text).finish
|
157
|
+
end
|
158
|
+
|
159
|
+
def op_endpoint
|
160
|
+
if @request.url =~ /(.*\?openid.success=true)/
|
161
|
+
$1
|
162
|
+
elsif @request.url =~ /([^?]*)/
|
163
|
+
$1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rots
|
2
|
+
module Test
|
3
|
+
module RackTestHelpers
|
4
|
+
def mock_openid_request(app, *args)
|
5
|
+
env = Rack::MockRequest.env_for(*args)
|
6
|
+
@response = Rack::MockResponse.new(*app.call(env))
|
7
|
+
end
|
8
|
+
|
9
|
+
def follow_openid_redirect!(app)
|
10
|
+
assert(@response)
|
11
|
+
assert_equal(303, @response.status)
|
12
|
+
|
13
|
+
env = Rack::MockRequest.env_for(@response.headers["Location"])
|
14
|
+
_status, headers, _body = Rots::Mocks::RotsServer.new.call(env)
|
15
|
+
|
16
|
+
uri = URI(headers["Location"])
|
17
|
+
mock_openid_request(app, "#{uri.path}?#{uri.query}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "rack/utils"
|
2
|
+
|
3
|
+
module Rots
|
4
|
+
module Test
|
5
|
+
module RequestHelper
|
6
|
+
def openid_request(openid_request_uri)
|
7
|
+
openid_response = Net::HTTP.get_response(URI.parse(openid_request_uri))
|
8
|
+
openid_response_uri = URI(openid_response["Location"])
|
9
|
+
openid_response_qs = Rack::Utils.parse_query(openid_response_uri.query)
|
10
|
+
|
11
|
+
{
|
12
|
+
url: openid_response_uri.to_s,
|
13
|
+
query_params: openid_response_qs,
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def checkid_setup(request, params = {}, with_associate = true)
|
18
|
+
assoc_handle = make_association(request) if with_associate
|
19
|
+
send_checkid(request, :setup, params, assoc_handle)
|
20
|
+
end
|
21
|
+
|
22
|
+
def checkid_immediate(request, params = {}, with_associate = true)
|
23
|
+
assoc_handle = make_association(request) if with_associate
|
24
|
+
send_checkid(request, :immediate, params, assoc_handle)
|
25
|
+
end
|
26
|
+
|
27
|
+
def openid_params(response)
|
28
|
+
uri = URI(response.headers["Location"])
|
29
|
+
Rack::Utils.parse_query(uri.query)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def send_checkid(request, mode, params = {}, assoc_handle = nil)
|
35
|
+
params = send(:"checkid_#{mode}_params", params)
|
36
|
+
params.merge("openid.assoc_handle" => assoc_handle) if assoc_handle
|
37
|
+
qs = "/?" + Rack::Utils.build_query(params)
|
38
|
+
request.get(qs)
|
39
|
+
end
|
40
|
+
|
41
|
+
def make_association(request)
|
42
|
+
associate_qs = Rack::Utils.build_query(associate_params)
|
43
|
+
response = request.post("/", input: associate_qs)
|
44
|
+
parse_assoc_handle_from(response)
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_assoc_handle_from(response)
|
48
|
+
response.body.split("\n")[0].match(/^assoc_handle:(.*)$/).captures[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
def checkid_setup_params(params = {})
|
52
|
+
{
|
53
|
+
"openid.ns" => "http://specs.openid.net/auth/2.0",
|
54
|
+
"openid.mode" => "checkid_setup",
|
55
|
+
"openid.claimed_id" => "john.doe",
|
56
|
+
"openid.identity" => "john.doe",
|
57
|
+
"openid.return_to" => "http://www.google.com",
|
58
|
+
# need to specify the openid_handle by hand
|
59
|
+
}.merge!(params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def checkid_immediate_params(params = {})
|
63
|
+
checkid_setup_params({"openid.mode" => "checkid_immediate"}.merge!(params))
|
64
|
+
end
|
65
|
+
|
66
|
+
def associate_params
|
67
|
+
{
|
68
|
+
"openid.ns" => "http://specs.openid.net/auth/2.0",
|
69
|
+
"openid.mode" => "associate",
|
70
|
+
"openid.session_type" => "DH-SHA1",
|
71
|
+
"openid.assoc_type" => "HMAC-SHA1",
|
72
|
+
"openid.dh_consumer_public" =>
|
73
|
+
"U672/RsDUNxAFFAXA+ShVh5LMD2CRdsoqdqhDCPUzfCNy2f44uTWuid/MZuGfJmiVA7QmxqM3GSb8EVq3SGK8eGEwwyzUtatqHidx72rfwAav5AUrZTnwSPQJyiCFrKNGmNhXdRJzcfzSkgaC3hVz2kpADzEevIExG6agns1sYY=",
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/rots/test.rb
ADDED
data/lib/rots/version.rb
ADDED
data/lib/rots.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# stdlib in Ruby < 3, gem after
|
2
|
+
require "net/http"
|
3
|
+
|
4
|
+
# External Libraries
|
5
|
+
require "date"
|
6
|
+
require "openssl"
|
7
|
+
require "optparse"
|
8
|
+
require "rack"
|
9
|
+
require "rackup"
|
10
|
+
require "openid" # ruby-openid2
|
11
|
+
require "stringio"
|
12
|
+
require "webrick"
|
13
|
+
require "yaml"
|
14
|
+
require "psych"
|
15
|
+
|
16
|
+
# This library
|
17
|
+
require_relative "rots/version"
|
18
|
+
require_relative "rots/server_app"
|
19
|
+
require_relative "rots/identity_page_app"
|
20
|
+
|
21
|
+
# Namespace for this gem
|
22
|
+
module Rots
|
23
|
+
end
|
24
|
+
|
25
|
+
Rots::Version.class_eval do
|
26
|
+
extend VersionGem::Basic
|
27
|
+
end
|
data.tar.gz.sig
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
������%P{ޱ��3܅`�/\���z��%͐z��4r��-��`�Z�a�nՑm,���c�Z~mC:ϚB�u��m3�ȟ� ���f�,O��
|
2
|
+
������>��r�ُ;K�
|
3
|
+
�v5�=�תEj��w'U}����f��*7�*�䰍����5���*��e�m~6��<V��"�0 (�'��b���D9,{�4�eV � �qd���9%@2buwˤ�F��L<�!~���KG����ض�l��N�u��i�?�K_��
|
4
|
+
}P?K�F��~[��tz��֫)K����Jz�uƪ��Y-]�Lݛ������M`��9�a*(�2���3Kw�6\q�#-��*&��&��&��
|
5
|
+
#�fy��+?&~'�$FL]�.~��5��%��
|