Mercurial > openid
comparison src/ur/openid.ur @ 6:99496175078b
Added preliminary versions of all the authentication verification steps
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Mon, 27 Dec 2010 13:18:02 -0500 |
parents | 443f27cd1572 |
children | 976121190b2d |
comparison
equal
deleted
inserted
replaced
5:443f27cd1572 | 6:99496175078b |
---|---|
1 val discoveryExpiry = 3600 | |
2 val nonceExpiry = 3600 | |
3 | |
1 task initialize = fn () => OpenidFfi.init | 4 task initialize = fn () => OpenidFfi.init |
2 | 5 |
6 table discoveries : { Identifier : string, Endpoint : string, Expires : time } | |
7 PRIMARY KEY Identifier | |
8 | |
3 fun discover s = | 9 fun discover s = |
4 r <- OpenidFfi.discover s; | 10 endpoint <- oneOrNoRowsE1 (SELECT (discoveries.Endpoint) |
5 return (Option.mp (fn r => {Endpoint = OpenidFfi.endpoint r, | 11 FROM discoveries |
6 LocalId = OpenidFfi.localId r}) r) | 12 WHERE discoveries.Identifier = {[s]}); |
13 case endpoint of | |
14 Some ep => return (Some ep) | |
15 | None => | |
16 r <- OpenidFfi.discover s; | |
17 case r of | |
18 None => return None | |
19 | Some r => | |
20 tm <- now; | |
21 dml (INSERT INTO discoveries (Identifier, Endpoint, Expires) | |
22 VALUES ({[s]}, {[OpenidFfi.endpoint r]}, {[addSeconds tm discoveryExpiry]})); | |
23 return (Some (OpenidFfi.endpoint r)) | |
7 | 24 |
8 val createInputs = | 25 val createInputs = |
9 is <- OpenidFfi.createInputs; | 26 is <- OpenidFfi.createInputs; |
10 OpenidFfi.addInput is "openid.ns" "http://specs.openid.net/auth/2.0"; | 27 OpenidFfi.addInput is "openid.ns" "http://specs.openid.net/auth/2.0"; |
11 return is | 28 return is |
12 | 29 |
13 table associations : { Endpoint : string, Handle : string, Key : string, Expires : time } | 30 table associations : { Endpoint : string, Handle : string, Key : string, Expires : time } |
14 PRIMARY KEY Endpoint | 31 PRIMARY KEY Endpoint |
15 | |
16 task periodic 1 = fn () => dml (DELETE FROM associations WHERE Expires < CURRENT_TIMESTAMP) | |
17 | 32 |
18 datatype association = Association of {Handle : string, Key : string} | AssError of string | 33 datatype association = Association of {Handle : string, Key : string} | AssError of string |
19 | 34 |
20 fun association url = | 35 fun association url = |
21 secret <- oneOrNoRows1 (SELECT associations.Handle, associations.Key | 36 secret <- oneOrNoRows1 (SELECT associations.Handle, associations.Key |
42 dml (INSERT INTO associations (Endpoint, Handle, Key, Expires) | 57 dml (INSERT INTO associations (Endpoint, Handle, Key, Expires) |
43 VALUES ({[url]}, {[handle]}, {[key]}, {[addSeconds tm expires]})); | 58 VALUES ({[url]}, {[handle]}, {[key]}, {[addSeconds tm expires]})); |
44 return (Association {Handle = handle, Key = key})) | 59 return (Association {Handle = handle, Key = key})) |
45 | _ => return (AssError "Missing fields in response from OP") | 60 | _ => return (AssError "Missing fields in response from OP") |
46 | 61 |
62 fun eatFragment s = | |
63 case String.split s #"#" of | |
64 Some (_, s') => s' | |
65 | _ => s | |
66 | |
67 datatype handle_result = HandleOk of string | HandleError of string | |
68 | |
69 fun verifyHandle os id = | |
70 ep <- discover (eatFragment id); | |
71 case ep of | |
72 None => return (HandleError "Discovery failed on returned endpoint") | |
73 | Some ep => | |
74 case OpenidFfi.getOutput os "openid.assoc_handle" of | |
75 None => return (HandleError "Missing association handle in response") | |
76 | Some handle => | |
77 assoc <- association ep; | |
78 case assoc of | |
79 AssError s => return (HandleError s) | |
80 | Association assoc => | |
81 if assoc.Handle <> handle then | |
82 return (HandleError "Association handles don't match") | |
83 else | |
84 return (HandleOk ep) | |
85 | |
86 table nonces : { Endpoint : string, Nonce : string, Expires : time } | |
87 PRIMARY KEY (Endpoint, Nonce) | |
88 | |
89 fun timeOfNonce s = | |
90 case String.split s #"T" of | |
91 None => None | |
92 | Some (date, s) => | |
93 case String.split s #"Z" of | |
94 None => None | |
95 | Some (time, _) => read (date ^ " " ^ time) | |
96 | |
97 fun verifyNonce os ep = | |
98 case OpenidFfi.getOutput os "openid.response_nonce" of | |
99 None => return (Some "Missing nonce in OP response") | |
100 | Some nonce => | |
101 case timeOfNonce nonce of | |
102 None => return (Some "Invalid timestamp in nonce") | |
103 | Some tm => | |
104 now <- now; | |
105 exp <- return (addSeconds now nonceExpiry); | |
106 if tm < exp then | |
107 return (Some "Nonce timestamp is too old") | |
108 else | |
109 b <- oneRowE1 (SELECT COUNT( * ) > 0 | |
110 FROM nonces | |
111 WHERE nonces.Endpoint = {[ep]} | |
112 AND nonces.Nonce = {[nonce]}); | |
113 | |
114 if b then | |
115 return (Some "Duplicate nonce") | |
116 else | |
117 dml (INSERT INTO nonces (Endpoint, Nonce, Expires) | |
118 VALUES ({[ep]}, {[nonce]}, {[exp]})); | |
119 return None | |
120 | |
121 fun verifySig os = | |
122 case OpenidFfi.getOutput os "openid.signed" of | |
123 None => return (Some "Missing openid.signed in OP response") | |
124 | Some signed => | |
125 case OpenidFfi.getOutput os "openid.sig" of | |
126 None => return (Some "Missing openid.sig in OP response") | |
127 | Some sign => let | |
128 fun gatherNvps signed acc = | |
129 let | |
130 val (this, next) = | |
131 case String.split signed #"," of | |
132 None => (signed, None) | |
133 | Some (this, next) => (this, Some next) | |
134 in | |
135 case OpenidFfi.getOutput os ("openid." ^ this) of | |
136 None => None | |
137 | Some value => | |
138 let | |
139 val acc = acc ^ this ^ ":" ^ value ^ "\n" | |
140 in | |
141 case next of | |
142 None => Some acc | |
143 | Some next => gatherNvps next acc | |
144 end | |
145 end | |
146 in | |
147 case gatherNvps signed "" of | |
148 None => return (Some "openid.signed mentions missing field") | |
149 | Some nvps => | |
150 let | |
151 val sign' = OpenidFfi.sha256 nvps | |
152 in | |
153 debug ("Fields: " ^ signed); | |
154 debug ("Nvps: " ^ nvps); | |
155 debug ("His: " ^ sign); | |
156 debug ("Mine: " ^ sign'); | |
157 if sign' = sign then | |
158 return None | |
159 else | |
160 return (Some "Signatures don't match") | |
161 end | |
162 end | |
163 | |
47 fun returnTo (qs : option queryString) = | 164 fun returnTo (qs : option queryString) = |
48 case qs of | 165 case qs of |
49 None => error <xml>Empty query string for OpenID callback</xml> | 166 None => error <xml>Empty query string for OpenID callback</xml> |
50 | Some qs => | 167 | Some qs => |
51 os <- OpenidFfi.indirect qs; | 168 os <- OpenidFfi.indirect qs; |
52 case OpenidFfi.getOutput os "openid.error" of | 169 case OpenidFfi.getOutput os "openid.error" of |
53 Some v => error <xml>Authentication failed: {[v]}</xml> | 170 Some v => error <xml>Authentication failed: {[v]}</xml> |
54 | None => | 171 | None => |
55 case OpenidFfi.getOutput os "openid.mode" of | 172 case OpenidFfi.getOutput os "openid.mode" of |
56 None => error <xml>No <tt>openid.mode</tt> in response</xml> | 173 None => error <xml>No <tt>openid.mode</tt> in response ({[qs]})</xml> |
57 | Some mode => | 174 | Some mode => |
58 case mode of | 175 case mode of |
59 "cancel" => error <xml>You canceled the authentication!</xml> | 176 "cancel" => error <xml>You canceled the authentication!</xml> |
60 | "id_res" => | 177 | "id_res" => |
61 (case OpenidFfi.getOutput os "openid.identity" of | 178 (case OpenidFfi.getOutput os "openid.identity" of |
62 None => error <xml>Missing identity in OP response</xml> | 179 None => error <xml>Missing identity in OP response</xml> |
63 | Some v => return <xml>Identity: {[v]}</xml>) | 180 | Some id => |
181 errO <- verifyHandle os id; | |
182 case errO of | |
183 HandleError s => error <xml>{[s]}</xml> | |
184 | HandleOk ep => | |
185 errO <- verifyReturnTo os; | |
186 case errO of | |
187 Some s => error <xml>{[s]}</xml> | |
188 | None => | |
189 errO <- verifyNonce os ep; | |
190 case errO of | |
191 Some s => error <xml>{[s]}</xml> | |
192 | None => | |
193 errO <- verifySig os; | |
194 case errO of | |
195 Some s => error <xml>{[s]}</xml> | |
196 | None => return <xml>Identity: {[id]}</xml>) | |
64 | _ => error <xml>Unexpected <tt>openid.mode</tt>: <tt>{[mode]}</tt></xml> | 197 | _ => error <xml>Unexpected <tt>openid.mode</tt>: <tt>{[mode]}</tt></xml> |
198 | |
199 and verifyReturnTo os = | |
200 case OpenidFfi.getOutput os "openid.return_to" of | |
201 None => return (Some "Missing return_to in OP response") | |
202 | Some rt => | |
203 if rt <> show (effectfulUrl returnTo) then | |
204 return (Some "Wrong return_to in OP response") | |
205 else | |
206 return None | |
65 | 207 |
66 fun authenticate id = | 208 fun authenticate id = |
67 dy <- discover id; | 209 dy <- discover id; |
68 case dy of | 210 case dy of |
69 None => return "Discovery failed" | 211 None => return "Discovery failed" |
70 | Some dy => | 212 | Some dy => |
71 assoc <- association dy.Endpoint; | 213 assoc <- association dy; |
72 case assoc of | 214 case assoc of |
73 AssError msg => return msg | 215 AssError msg => return msg |
74 | Association assoc => | 216 | Association assoc => |
75 redirect (bless (dy.Endpoint ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id=" | 217 redirect (bless (dy ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id=" |
76 ^ id ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.assoc_handle=" | 218 ^ id ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.assoc_handle=" |
77 ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo))) | 219 ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo))) |
220 | |
221 task periodic 1 = fn () => | |
222 dml (DELETE FROM discoveries WHERE Expires < CURRENT_TIMESTAMP); | |
223 dml (DELETE FROM associations WHERE Expires < CURRENT_TIMESTAMP); | |
224 dml (DELETE FROM nonces WHERE Expires < CURRENT_TIMESTAMP) |