annotate jamendo/sparql-archived/SeRQL/lib/http/http_openid.pl @ 27:d95e683fbd35 tip

Enable CORS on urispace redirects as well
author Chris Cannam
date Tue, 20 Feb 2018 14:52:02 +0000
parents df9685986338
children
rev   line source
Chris@0 1 /* $Id$
Chris@0 2
Chris@0 3 Part of SWI-Prolog
Chris@0 4
Chris@0 5 Author: Jan Wielemaker
Chris@0 6 E-mail: wielemak@science.uva.nl
Chris@0 7 WWW: http://www.swi-prolog.org
Chris@0 8 Copyright (C): 2007, University of Amsterdam
Chris@0 9
Chris@0 10 This program is free software; you can redistribute it and/or
Chris@0 11 modify it under the terms of the GNU General Public License
Chris@0 12 as published by the Free Software Foundation; either version 2
Chris@0 13 of the License, or (at your option) any later version.
Chris@0 14
Chris@0 15 This program is distributed in the hope that it will be useful,
Chris@0 16 but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 18 GNU General Public License for more details.
Chris@0 19
Chris@0 20 You should have received a copy of the GNU Lesser General Public
Chris@0 21 License along with this library; if not, write to the Free Software
Chris@0 22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Chris@0 23
Chris@0 24 As a special exception, if you link this library with other files,
Chris@0 25 compiled with a Free Software compiler, to produce an executable, this
Chris@0 26 library does not by itself cause the resulting executable to be covered
Chris@0 27 by the GNU General Public License. This exception does not however
Chris@0 28 invalidate any other reasons why the executable file might be covered by
Chris@0 29 the GNU General Public License.
Chris@0 30 */
Chris@0 31
Chris@0 32 :- module(http_openid,
Chris@0 33 [ openid_login/1, % +OpenID
Chris@0 34 openid_logout/1, % +OpenID
Chris@0 35 openid_logged_in/1, % -OpenID
Chris@0 36
Chris@0 37 % transparent login
Chris@0 38 openid_user/3, % +Request, -User, +Options
Chris@0 39
Chris@0 40 % low-level primitives
Chris@0 41 openid_verify/2, % +Options, +Request
Chris@0 42 openid_authenticate/4, % +Request, -Server, -User, -ReturnTo
Chris@0 43 openid_associate/3, % +OpenIDServer, -Handle, -Association
Chris@0 44 openid_server/2, % +Request
Chris@0 45 openid_grant/1, % +Request
Chris@0 46 openid_file/1, % +Request (?name=File)
Chris@0 47 openid_server/3, % ?OpenIDLogin, ?OpenID, ?Server
Chris@0 48
Chris@0 49 openid_login_form/4, % +ReturnTo, +Options, //
Chris@0 50 openid_css/2, % +Emit link to CSS page
Chris@0 51
Chris@0 52 openid_current_host/3 % +Request, -Host, -Port
Chris@0 53 ]).
Chris@0 54 :- use_module(library('http/http_open')).
Chris@0 55 :- use_module(library('http/http_client')).
Chris@0 56 :- use_module(library('http/html_write')).
Chris@0 57 :- use_module(library('http/http_parameters')).
Chris@0 58 :- use_module(library('http/http_wrapper')).
Chris@0 59 :- use_module(library('http/thread_httpd')).
Chris@0 60 :- use_module(library('http/http_dispatch')).
Chris@0 61 :- use_module(library('http/http_session')).
Chris@0 62 :- use_module(library('http/http_host')).
Chris@0 63 :- use_module(library(utf8)).
Chris@0 64 :- use_module(library(error)).
Chris@0 65 :- use_module(library(sgml)).
Chris@0 66 :- use_module(library(url)).
Chris@0 67 :- use_module(library(occurs)).
Chris@0 68 :- use_module(library(base64)).
Chris@0 69 :- use_module(library(debug)).
Chris@0 70 :- use_module(library(record)).
Chris@0 71 :- use_module(library(option)).
Chris@0 72 :- use_module(library(sha)).
Chris@0 73 :- use_module(library(socket)).
Chris@0 74 :- use_module(library(lists)).
Chris@0 75
Chris@0 76
Chris@0 77 /** <module> OpenID consumer library
Chris@0 78
Chris@0 79 This library implements the OpenID protocol (http://openid.net/). OpenID
Chris@0 80 is a protocol to share identities on the network. The protocol itself
Chris@0 81 uses simple basic HTTP, adding reliability using digitally signed
Chris@0 82 messages.
Chris@0 83
Chris@0 84 Steps, as seen from the _consumer_ (or _|relying partner|_).
Chris@0 85
Chris@0 86 1. Show login form, asking for =openid_identifier=
Chris@0 87 2. Get HTML page from =openid_identifier= and lookup
Chris@0 88 =|<link rel="openid.server" href="server">|=
Chris@0 89 3. Associate to _server_
Chris@0 90 4. Redirect browser (302) to server using mode =checkid_setup=,
Chris@0 91 asking to validate the given OpenID.
Chris@0 92 5. OpenID server redirects back, providing digitally signed
Chris@0 93 conformation of the claimed identity.
Chris@0 94 6. Validate signature and redirect to the target location.
Chris@0 95
Chris@0 96 This module is typically used through openid_user/3.
Chris@0 97
Chris@0 98 @author Jan Wielemaker
Chris@0 99 */
Chris@0 100
Chris@0 101 %% openid_hook(+Action)
Chris@0 102 %
Chris@0 103 % Call hook on the OpenID management library. Defined hooks are:
Chris@0 104 %
Chris@0 105 % * login(+OpenID)
Chris@0 106 % Consider OpenID logged in.
Chris@0 107 %
Chris@0 108 % * logout(+OpenID)
Chris@0 109 % Logout OpenID
Chris@0 110 %
Chris@0 111 % * logged_in(?OpenID)
Chris@0 112 % True if OpenID is logged in
Chris@0 113 %
Chris@0 114 % * grant(+Request, +Options)
Chris@0 115 % Server: Reply positive on OpenID
Chris@0 116 %
Chris@0 117 % * trusted_server(?Server)
Chris@0 118 % True if Server is a trusted OpenID server
Chris@0 119
Chris@0 120 :- multifile
Chris@0 121 openid_hook/1. % +Action
Chris@0 122
Chris@0 123 /*******************************
Chris@0 124 * DIRECT LOGIN/OUT *
Chris@0 125 *******************************/
Chris@0 126
Chris@0 127 %% openid_login(+OpenID) is det.
Chris@0 128 %
Chris@0 129 % Associate the current HTTP session with OpenID. If another
Chris@0 130 % OpenID is already associated, this association is first removed.
Chris@0 131
Chris@0 132 openid_login(OpenID) :-
Chris@0 133 openid_hook(login(OpenID)), !.
Chris@0 134 openid_login(OpenID) :-
Chris@0 135 openid_logout(_),
Chris@0 136 http_session_assert(openid(OpenID)).
Chris@0 137
Chris@0 138 %% openid_logout(+OpenID) is det.
Chris@0 139 %
Chris@0 140 % Remove the association of the current session with any OpenID
Chris@0 141
Chris@0 142 openid_logout(OpenID) :-
Chris@0 143 openid_hook(logout(OpenID)), !.
Chris@0 144 openid_logout(OpenID) :-
Chris@0 145 http_session_retractall(openid(OpenID)).
Chris@0 146
Chris@0 147 %% openid_logged_in(-OpenID) is semidet.
Chris@0 148 %
Chris@0 149 % True if session is associated with OpenID.
Chris@0 150
Chris@0 151 openid_logged_in(OpenID) :-
Chris@0 152 openid_hook(logged_in(OpenID)), !.
Chris@0 153 openid_logged_in(OpenID) :-
Chris@0 154 http_session_data(openid(OpenID)).
Chris@0 155
Chris@0 156
Chris@0 157 /*******************************
Chris@0 158 * TOPLEVEL *
Chris@0 159 *******************************/
Chris@0 160
Chris@0 161 %% openid_user(+Request:http_request, -OpenID:url, +Options) is det.
Chris@0 162 %
Chris@0 163 % True if OpenID is a validated OpenID associated with the current
Chris@0 164 % session. The scenario for which this predicate is designed is to
Chris@0 165 % allow an HTTP handler that requires a valid login to
Chris@0 166 % use the transparent code below.
Chris@0 167 %
Chris@0 168 % ==
Chris@0 169 % handler(Request) :-
Chris@0 170 % openid_user(Request, OpenID, []),
Chris@0 171 % ...
Chris@0 172 % ==
Chris@0 173 %
Chris@0 174 % If the user is not yet logged on a sequence of redirects will
Chris@0 175 % follow:
Chris@0 176 %
Chris@0 177 % 1. Show a page for login (default: page /openid/login),
Chris@0 178 % predicate reply_openid_login/1)
Chris@0 179 % 2. Redirect to OpenID server to validate
Chris@0 180 % 3. Redirect to validation
Chris@0 181 %
Chris@0 182 % Options:
Chris@0 183 %
Chris@0 184 % * login_url(Login)
Chris@0 185 % (Local) URL of page to enter OpenID information. Default
Chris@0 186 % is =|/openid/login|=.
Chris@0 187 %
Chris@0 188 % @see openid_authenticate/4 produces errors if login is invalid
Chris@0 189 % or cancelled.
Chris@0 190
Chris@0 191 :- http_handler('/openid/login', openid_login_page, []).
Chris@0 192 :- http_handler('/openid/verify', openid_verify([]), []).
Chris@0 193
Chris@0 194 openid_user(_Request, OpenID, _Options) :-
Chris@0 195 openid_logged_in(OpenID), !.
Chris@0 196 openid_user(Request, User, _Options) :-
Chris@0 197 openid_authenticate(Request, _OpenIdServer, OpenID, _ReturnTo), !,
Chris@0 198 openid_server(User, OpenID, _),
Chris@0 199 openid_login(User).
Chris@0 200 openid_user(Request, _OpenID, Options) :-
Chris@0 201 option(login_url(Login), Options, '/openid/login'),
Chris@0 202 current_url(Request, Here),
Chris@0 203 redirect_browser(Login,
Chris@0 204 [ 'openid.return_to' = Here
Chris@0 205 ]).
Chris@0 206
Chris@0 207
Chris@0 208 %% openid_login_page(+Request) is det.
Chris@0 209 %
Chris@0 210 % Present a login-form for OpenID. There are two ways to redefine
Chris@0 211 % this default login page. One is to provide the option
Chris@0 212 % =login_url= to openid_user/3 and the other is to define a new
Chris@0 213 % handler for =|/openid/login|= using http_handler/3.
Chris@0 214 %
Chris@0 215 % @tbd Add CSS to page
Chris@0 216 % @tbd Use http_current_handler/2 to make the link more dynamic.
Chris@0 217
Chris@0 218 openid_login_page(Request) :-
Chris@0 219 http_parameters(Request,
Chris@0 220 [ 'openid.return_to'(ReturnTo, [])
Chris@0 221 ]),
Chris@0 222 reply_html_page([ title('OpenID login'),
Chris@0 223 \openid_css
Chris@0 224 ],
Chris@0 225 [ \openid_login_form(ReturnTo, [])
Chris@0 226 ]).
Chris@0 227
Chris@0 228 %% openid_css// is det.
Chris@0 229 %
Chris@0 230 % Emit a link to the OpenID CSS file.
Chris@0 231
Chris@0 232 openid_css -->
Chris@0 233 html(link([ rel(stylesheet),
Chris@0 234 type('text/css'),
Chris@0 235 href('file?name=openid_css')
Chris@0 236 ])).
Chris@0 237
Chris@0 238 %% openid_login_form(+ReturnTo, +Options)// is det.
Chris@0 239 %
Chris@0 240 % Create the OpenID form. This exported as a seperate DCG,
Chris@0 241 % allowing applications to redefine /openid/login and reuse this
Chris@0 242 % part of the page.
Chris@0 243
Chris@0 244 openid_login_form(ReturnTo, Options) -->
Chris@0 245 { option(action(Action), Options, verify)
Chris@0 246 },
Chris@0 247 html(div(class('openid-login'),
Chris@0 248 [ \openid_title,
Chris@0 249 form([ name(login),
Chris@0 250 action(Action),
Chris@0 251 method('GET')
Chris@0 252 ],
Chris@0 253 [ \hidden('openid.return_to', ReturnTo),
Chris@0 254 div([ input([ class('openid-input'),
Chris@0 255 name(openid_url),
Chris@0 256 size(30)
Chris@0 257 ]),
Chris@0 258 input([ type(submit),
Chris@0 259 value('Verify!')
Chris@0 260 ])
Chris@0 261 ])
Chris@0 262 ])
Chris@0 263 ])).
Chris@0 264
Chris@0 265
Chris@0 266
Chris@0 267 /*******************************
Chris@0 268 * HTTP REPLIES *
Chris@0 269 *******************************/
Chris@0 270
Chris@0 271 %% openid_verify(+Options, +Request)
Chris@0 272 %
Chris@0 273 % Handle the initial login form presented to the user by the
Chris@0 274 % relying party (consumer). This predicate discovers the OpenID
Chris@0 275 % server, associates itself with this server and redirects the
Chris@0 276 % user's browser to the OpenID server, providing the extra
Chris@0 277 % openid.X name-value pairs. Options is, against the conventions,
Chris@0 278 % placed in front of the Request to allow for smooth cooperation
Chris@0 279 % with http_dispatch.pl.
Chris@0 280 %
Chris@0 281 % The OpenId server will redirect to the openid.return_to URL.
Chris@0 282 %
Chris@0 283 % @throws http_reply(moved_temporary(Redirect))
Chris@0 284
Chris@0 285 openid_verify(Options, Request) :-
Chris@0 286 http_parameters(Request,
Chris@0 287 [ openid_url(URL, [length>1]),
Chris@0 288 'openid.return_to'(ReturnTo0, [optional(true)])
Chris@0 289 ]),
Chris@0 290 ( option(return_to(ReturnTo1), Options) % Option
Chris@0 291 -> current_url(Request, CurrentLocation),
Chris@0 292 global_url(ReturnTo1, CurrentLocation, ReturnTo)
Chris@0 293 ; nonvar(ReturnTo0)
Chris@0 294 -> ReturnTo = ReturnTo0 % Form-data
Chris@0 295 ; current_url(Request, CurrentLocation),
Chris@0 296 ReturnTo = CurrentLocation % Current location
Chris@0 297 ),
Chris@0 298 current_root_url(Request, CurrentRoot),
Chris@0 299 option(trust_root(TrustRoot), Options, CurrentRoot),
Chris@0 300 openid_resolve(URL, OpenIDLogin, OpenID, Server),
Chris@0 301 trusted(OpenID, Server),
Chris@0 302 openid_associate(Server, Handle, _Assoc),
Chris@0 303 assert_openid(OpenIDLogin, OpenID, Server),
Chris@0 304 redirect_browser(Server, [ 'openid.mode' = checkid_setup,
Chris@0 305 'openid.identity' = OpenID,
Chris@0 306 'openid.assoc_handle' = Handle,
Chris@0 307 'openid.return_to' = ReturnTo,
Chris@0 308 'openid.trust_root' = TrustRoot
Chris@0 309 ]).
Chris@0 310
Chris@0 311
Chris@0 312 %% assert_openid(+OpenIDLogin, +OpenID, +Server) is det.
Chris@0 313 %
Chris@0 314 % Associate the OpenID as typed by the user, the OpenID as
Chris@0 315 % validated by the Server with the current HTTP session.
Chris@0 316 %
Chris@0 317 % @param OpenIDLogin Canonized OpenID typed by user
Chris@0 318 % @param OpenID OpenID verified by Server.
Chris@0 319
Chris@0 320 assert_openid(OpenIDLogin, OpenID, Server) :-
Chris@0 321 http_session_assert(openid_login(OpenIDLogin, OpenID, Server)).
Chris@0 322
Chris@0 323 %% openid_server(?OpenIDLogin, ?OpenID, ?Server) is nondet.
Chris@0 324 %
Chris@0 325 % True if OpenIDLogin is the typed id for OpenID verified by
Chris@0 326 % Server.
Chris@0 327 %
Chris@0 328 % @param OpenIDLogin ID as typed by user (canonized)
Chris@0 329 % @param OpenID ID as verified by server
Chris@0 330 % @param Server URL of the OpenID server
Chris@0 331
Chris@0 332 openid_server(OpenIDLogin, OpenID, Server) :-
Chris@0 333 http_session_data(openid_login(OpenIDLogin, OpenID, Server)), !.
Chris@0 334
Chris@0 335
Chris@0 336 %% current_url(+Request, -Root) is det.
Chris@0 337 %% current_root_url(+Request, -Root) is det.
Chris@0 338 %
Chris@0 339 % Return URL of current request or current root.
Chris@0 340
Chris@0 341 current_root_url(Request, Root) :-
Chris@0 342 openid_current_host(Request, Host, Port),
Chris@0 343 parse_url(Root, [protocol(http), host(Host), port(Port), path(/)]).
Chris@0 344
Chris@0 345 current_url(Request, URL) :-
Chris@0 346 openid_current_host(Request, Host, Port),
Chris@0 347 ( option(x_redirected_path(Path), Request)
Chris@0 348 -> true
Chris@0 349 ; option(path(Path), Request, /)
Chris@0 350 ),
Chris@0 351 option(search(Search), Request, []),
Chris@0 352 parse_url(URL, [ protocol(http), host(Host), port(Port),
Chris@0 353 path(Path), search(Search)
Chris@0 354 ]).
Chris@0 355
Chris@0 356 %% openid_current_host(Request, Host, Port)
Chris@0 357 %
Chris@0 358 % Find current location of the server.
Chris@0 359
Chris@0 360 openid_current_host(Request, Host, Port) :-
Chris@0 361 http_current_host(Request, Host, Port,
Chris@0 362 [ global(true)
Chris@0 363 ]).
Chris@0 364
Chris@0 365
Chris@0 366 %% redirect_browser(+URL, +FormExtra)
Chris@0 367 %
Chris@0 368 % Generate a 302 temporary redirect to URL, adding the extra form
Chris@0 369 % information from FormExtra. The specs says we must retain the
Chris@0 370 % search specification already attached to the URL.
Chris@0 371
Chris@0 372 redirect_browser(URL, FormExtra) :-
Chris@0 373 is_absolute_url(URL), !,
Chris@0 374 parse_url(URL, Parts0),
Chris@0 375 ( select(search(List), Parts0, Parts1)
Chris@0 376 -> append(List, FormExtra, Search),
Chris@0 377 Parts = [search(Search)|Parts1]
Chris@0 378 ; Parts = [search(FormExtra)|Parts0]
Chris@0 379 ),
Chris@0 380 parse_url(Redirect, Parts),
Chris@0 381 throw(http_reply(moved_temporary(Redirect))).
Chris@0 382 redirect_browser(Location, FormExtra) :-
Chris@0 383 http_location(Parts0, Location),
Chris@0 384 ( select(search(List), Parts0, Parts1)
Chris@0 385 -> append(List, FormExtra, Search),
Chris@0 386 Parts = [search(Search)|Parts1]
Chris@0 387 ; Parts = [search(FormExtra)|Parts0]
Chris@0 388 ),
Chris@0 389 http_location(Parts, Redirect),
Chris@0 390 throw(http_reply(moved_temporary(Redirect))).
Chris@0 391
Chris@0 392
Chris@0 393 /*******************************
Chris@0 394 * RESOLVE *
Chris@0 395 *******************************/
Chris@0 396
Chris@0 397 %% openid_resolve(+URL, -OpenIDOrig, -OpenID, -Server)
Chris@0 398 %
Chris@0 399 % True if OpenID is the claimed OpenID that belongs to URL and
Chris@0 400 % Server is the URL of the OpenID server that can be asked to
Chris@0 401 % verify this claim.
Chris@0 402 %
Chris@0 403 % @param URL The OpenID typed by the user
Chris@0 404 % @param OpenIDOrig Canonized OpenID typed by user
Chris@0 405 % @param OpenID Possibly delegated OpenID
Chris@0 406 % @param Server OpenID server that must validate OpenID
Chris@0 407 %
Chris@0 408 % @tbd Implement complete URL canonization as defined by the
Chris@0 409 % OpenID 2.0 proposal.
Chris@0 410
Chris@0 411 openid_resolve(URL, OpenID0, OpenID, Server) :-
Chris@0 412 debug(openid(resolve), 'Opening ~w ...', [URL]),
Chris@0 413 http_open(URL, Stream,
Chris@0 414 [ final_url(OpenID0)
Chris@0 415 ]),
Chris@0 416 dtd(html, DTD),
Chris@0 417 call_cleanup(load_structure(Stream, Term,
Chris@0 418 [ dtd(DTD),
Chris@0 419 dialect(sgml),
Chris@0 420 shorttag(false),
Chris@0 421 syntax_errors(quiet)
Chris@0 422 ]),
Chris@0 423 close(Stream)),
Chris@0 424 debug(openid(resolve), 'Scanning HTML document ...', [URL]),
Chris@0 425 contains_term(element(head, _, Head), Term),
Chris@0 426 ( link(Head, 'openid.server', Server)
Chris@0 427 -> debug(openid(resolve), 'OpenID Server=~q', [Server])
Chris@0 428 ; debug(openid(resolve), 'No server in ~q', [Head]),
Chris@0 429 fail
Chris@0 430 ),
Chris@0 431 ( link(Head, 'openid.delegate', OpenID)
Chris@0 432 -> debug(openid(resolve), 'OpenID = ~q (delegated)', [OpenID])
Chris@0 433 ; OpenID = OpenID0,
Chris@0 434 debug(openid(resolve), 'OpenID = ~q', [OpenID])
Chris@0 435 ).
Chris@0 436
Chris@0 437
Chris@0 438 link(DOM, Type, Target) :-
Chris@0 439 sub_term(element(link, Attrs, []), DOM),
Chris@0 440 memberchk(rel=Type, Attrs),
Chris@0 441 memberchk(href=Target, Attrs).
Chris@0 442
Chris@0 443
Chris@0 444 /*******************************
Chris@0 445 * AUTHENTICATE *
Chris@0 446 *******************************/
Chris@0 447
Chris@0 448
Chris@0 449 %% openid_authenticate(+Request, -Server:url, -OpenID:url,
Chris@0 450 %% -ReturnTo:url) is semidet.
Chris@0 451 %
Chris@0 452 % Succeeds if Request comes from the OpenID server and confirms
Chris@0 453 % that User is a verified OpenID user. ReturnTo provides the URL
Chris@0 454 % to return to.
Chris@0 455 %
Chris@0 456 % After openid_verify/2 has redirected the browser to the OpenID
Chris@0 457 % server, and the OpenID server did its magic, it redirects the
Chris@0 458 % browser back to this address. The work is fairly trivial. If
Chris@0 459 % =mode= is =cancel=, the OpenId server denied. If =id_res=, the
Chris@0 460 % OpenId server replied positive, but we must verify what the
Chris@0 461 % server tells us by checking the HMAC-SHA signature.
Chris@0 462 %
Chris@0 463 % This call fails silently if their is no =|openid.mode|= field in
Chris@0 464 % the request.
Chris@0 465 %
Chris@0 466 % @throws openid(cancel)
Chris@0 467 % if request was cancelled by the OpenId server
Chris@0 468 % @throws openid(signature_mismatch)
Chris@0 469 % if the HMAC signature check failed
Chris@0 470
Chris@0 471 openid_authenticate(Request, OpenIdServer, Identity, ReturnTo) :-
Chris@0 472 http_parameters(Request,
Chris@0 473 [ 'openid.mode'(Mode, [optional(true)])
Chris@0 474 ]),
Chris@0 475 ( var(Mode)
Chris@0 476 -> fail
Chris@0 477 ; Mode == cancel
Chris@0 478 -> throw(openid(cancel))
Chris@0 479 ; Mode == id_res
Chris@0 480 -> http_parameters(Request,
Chris@0 481 [ 'openid.identity'(Identity, []),
Chris@0 482 'openid.assoc_handle'(Handle, []),
Chris@0 483 'openid.return_to'(ReturnTo, []),
Chris@0 484 'openid.signed'(AtomFields, []),
Chris@0 485 'openid.sig'(Base64Signature, []),
Chris@0 486 'openid.invalidate_handle'(Invalidate,
Chris@0 487 [optional(true)])
Chris@0 488 ],
Chris@0 489 [ form_data(Form)
Chris@0 490 ]),
Chris@0 491 concat_atom(SignedFields, ',', AtomFields),
Chris@0 492 check_obligatory_fields(SignedFields),
Chris@0 493 signed_pairs(SignedFields,
Chris@0 494 [ mode-Mode,
Chris@0 495 identity-Identity,
Chris@0 496 assoc_handle-Handle,
Chris@0 497 return_to-ReturnTo,
Chris@0 498 invalidate_handle-Invalidate
Chris@0 499 ],
Chris@0 500 Form,
Chris@0 501 SignedPairs),
Chris@0 502 ( openid_associate(OpenIdServer, Handle, Assoc)
Chris@0 503 -> signature(SignedPairs, Assoc, Sig)
Chris@0 504 ; existence_error(assoc_handle, Handle)
Chris@0 505 ),
Chris@0 506
Chris@0 507 atom_codes(Base64Signature, Base64SigCodes),
Chris@0 508 phrase(base64(Signature), Base64SigCodes),
Chris@0 509 ( Sig == Signature
Chris@0 510 -> true
Chris@0 511 ; throw(openid(signature_mismatch))
Chris@0 512 )
Chris@0 513 ).
Chris@0 514
Chris@0 515 %% signed_pairs(+FieldNames, +Pairs:list(Field-Value), +Form, -SignedPairs) is det.
Chris@0 516 %
Chris@0 517 % Extract the signed field in the order they appear in FieldNames.
Chris@0 518
Chris@0 519 signed_pairs([], _, _, []).
Chris@0 520 signed_pairs([Field|T0], Pairs, Form, [Field-Value|T]) :-
Chris@0 521 memberchk(Field-Value, Pairs), !,
Chris@0 522 signed_pairs(T0, Pairs, Form, T).
Chris@0 523 signed_pairs([Field|T0], Pairs, Form, [Field-Value|T]) :-
Chris@0 524 atom_concat('openid.', Field, OpenIdField),
Chris@0 525 memberchk(OpenIdField=Value, Form), !,
Chris@0 526 signed_pairs(T0, Pairs, Form, T).
Chris@0 527 signed_pairs([Field|T0], Pairs, Form, T) :-
Chris@0 528 format(user_error, 'Form = ~p~n', [Form]),
Chris@0 529 throw(error(existence_error(field, Field),
Chris@0 530 context(_, 'OpenID Signed field is not present'))),
Chris@0 531 signed_pairs(T0, Pairs, Form, T).
Chris@0 532
Chris@0 533
Chris@0 534 %% check_obligatory_fields(+SignedFields:list) is det.
Chris@0 535 %
Chris@0 536 % Verify fields from obligatory_field/1 are in the signed field
Chris@0 537 % list.
Chris@0 538 %
Chris@0 539 % @error existence_error(field, Field)
Chris@0 540
Chris@0 541 check_obligatory_fields(Fields) :-
Chris@0 542 ( obligatory_field(Field),
Chris@0 543 ( memberchk(Field, Fields)
Chris@0 544 -> true
Chris@0 545 ; throw(error(existence_error(field, Field),
Chris@0 546 context(_, 'OpenID field is not in signed fields')))
Chris@0 547 ),
Chris@0 548 fail
Chris@0 549 ; true
Chris@0 550 ).
Chris@0 551
Chris@0 552 obligatory_field(identity).
Chris@0 553
Chris@0 554
Chris@0 555 /*******************************
Chris@0 556 * OPENID SERVER *
Chris@0 557 *******************************/
Chris@0 558
Chris@0 559 :- dynamic
Chris@0 560 server_association/3. % URL, Handle, Term
Chris@0 561
Chris@0 562 %% openid_server(+Options, +Request)
Chris@0 563 %
Chris@0 564 % Realise the OpenID server. The protocol demands a POST request
Chris@0 565 % here.
Chris@0 566
Chris@0 567 openid_server(Options, Request) :-
Chris@0 568 http_parameters(Request,
Chris@0 569 [ 'openid.mode'(Mode)
Chris@0 570 ],
Chris@0 571 [ attribute_declarations(openid_attribute),
Chris@0 572 form_data(Form)
Chris@0 573 ]),
Chris@0 574 ( Mode == associate
Chris@0 575 -> associate_server(Request, Form, Options)
Chris@0 576 ; Mode == checkid_setup
Chris@0 577 -> checkid_setup_server(Request, Form, Options)
Chris@0 578 ).
Chris@0 579
Chris@0 580 %% associate_server(+Request, +Form, +Options)
Chris@0 581 %
Chris@0 582 % Handle the association-request. If successful, create a clause
Chris@0 583 % for server_association/3 to record the current association.
Chris@0 584
Chris@0 585 associate_server(Request, Form, Options) :-
Chris@0 586 memberchk('openid.assoc_type' = AssocType, Form),
Chris@0 587 memberchk('openid.session_type' = SessionType, Form),
Chris@0 588 memberchk('openid.dh_modulus' = P64, Form),
Chris@0 589 memberchk('openid.dh_gen' = G64, Form),
Chris@0 590 memberchk('openid.dh_consumer_public' = CPX64, Form),
Chris@0 591 base64_btwoc(P, P64),
Chris@0 592 base64_btwoc(G, G64),
Chris@0 593 base64_btwoc(CPX, CPX64),
Chris@0 594 dh_x(P, Y), % Our secret
Chris@0 595 DiffieHellman is powm(CPX, Y, P),
Chris@0 596 btwoc(DiffieHellman, DHBytes),
Chris@0 597 sha_hash(DHBytes, SHA1, [algorithm(sha1)]),
Chris@0 598 CPY is powm(G, Y, P),
Chris@0 599 base64_btwoc(CPY, CPY64),
Chris@0 600 new_assoc_handle(Handle),
Chris@0 601 random_bytes(20, MacKey),
Chris@0 602 xor_codes(MacKey, SHA1, EncKey),
Chris@0 603 phrase(base64(EncKey), Base64EncKey),
Chris@0 604 DefExpriresIn is 24*3600,
Chris@0 605 option(expires_in(ExpriresIn), Options, DefExpriresIn),
Chris@0 606
Chris@0 607 get_time(Now),
Chris@0 608 ExpiresAt is integer(Now+ExpriresIn),
Chris@0 609 make_association([ session_type(SessionType),
Chris@0 610 expires_at(ExpiresAt),
Chris@0 611 mac_key(MacKey)
Chris@0 612 ],
Chris@0 613 Record),
Chris@0 614 memberchk(peer(Peer), Request),
Chris@0 615 assert(server_association(Peer, Handle, Record)),
Chris@0 616
Chris@0 617 key_values_data([ assoc_type-AssocType,
Chris@0 618 assoc_handle-Handle,
Chris@0 619 expires_in-ExpriresIn,
Chris@0 620 session_type-SessionType,
Chris@0 621 dh_server_public-CPY64,
Chris@0 622 enc_mac_key-Base64EncKey
Chris@0 623 ],
Chris@0 624 Text),
Chris@0 625 format('Content-type: text/plain~n~n~s', [Text]).
Chris@0 626
Chris@0 627
Chris@0 628 new_assoc_handle(Handle) :-
Chris@0 629 random_bytes(20, Bytes),
Chris@0 630 phrase(base64(Bytes), HandleCodes),
Chris@0 631 atom_codes(Handle, HandleCodes).
Chris@0 632
Chris@0 633
Chris@0 634 %% checkid_setup_server(+Request, +Form, +Options)
Chris@0 635 %
Chris@0 636 % Validate an OpenID for a TrustRoot and redirect the browser back
Chris@0 637 % to the ReturnTo argument. There are many possible scenarios
Chris@0 638 % here:
Chris@0 639 %
Chris@0 640 % 1. Check some cookie and if present, grant immediately
Chris@0 641 % 2. Use a 401 challenge page
Chris@0 642 % 3. Present a normal grant/password page
Chris@0 643 % 4. As (3), but use HTTPS for the exchange
Chris@0 644 % 5. etc.
Chris@0 645 %
Chris@0 646 % First thing to check is the immediate acknowledgement.
Chris@0 647
Chris@0 648 checkid_setup_server(_Request, Form, _Options) :-
Chris@0 649 memberchk('openid.identity' = Identity, Form),
Chris@0 650 memberchk('openid.assoc_handle' = Handle, Form),
Chris@0 651 memberchk('openid.return_to' = ReturnTo, Form),
Chris@0 652 memberchk('openid.trust_root' = TrustRoot, Form),
Chris@0 653
Chris@0 654 server_association(_, Handle, _Association), % check
Chris@0 655
Chris@0 656 reply_html_page([ title('OpenID login'),
Chris@0 657 \openid_css
Chris@0 658 ],
Chris@0 659 [ \openid_title,
Chris@0 660 div(class('openid-message'),
Chris@0 661 ['Site ', a(href(TrustRoot), TrustRoot), ' requests permission \
Chris@0 662 to login with OpenID ', a(href(Identity), Identity), '.'
Chris@0 663 ]),
Chris@0 664 table(class('openid-form'),
Chris@0 665 [ tr(td(form([ action(grant), method('GET') ],
Chris@0 666 [ \hidden('openid.grant', yes),
Chris@0 667 \hidden('openid.identity', Identity),
Chris@0 668 \hidden('openid.assoc_handle', Handle),
Chris@0 669 \hidden('openid.return_to', ReturnTo),
Chris@0 670 \hidden('openid.trust_root', TrustRoot),
Chris@0 671 div(['Password: ',
Chris@0 672 input([type(password), name('openid.password')]),
Chris@0 673 input([type(submit), value('Grant')])
Chris@0 674 ])
Chris@0 675 ]))),
Chris@0 676 tr(td(align(right),
Chris@0 677 form([ action(grant), method('GET') ],
Chris@0 678 [ \hidden('openid.grant', no),
Chris@0 679 \hidden('openid.return_to', ReturnTo),
Chris@0 680 input([type(submit), value('Deny')])
Chris@0 681 ])))
Chris@0 682 ])
Chris@0 683 ]).
Chris@0 684
Chris@0 685 hidden(Name, Value) -->
Chris@0 686 html(input([type(hidden), name(Name), value(Value)])).
Chris@0 687
Chris@0 688
Chris@0 689 openid_title -->
Chris@0 690 html(div(class('openid-title'),
Chris@0 691 [ a(href('http://openid.net/'),
Chris@0 692 img([ src('file?name=openid_logo'), alt('OpenID') ])),
Chris@0 693 span('Login')
Chris@0 694 ])).
Chris@0 695
Chris@0 696
Chris@0 697 %% openid_grant(+Request)
Chris@0 698 %
Chris@0 699 % Handle the reply from checkid_setup_server/3. If the reply is
Chris@0 700 % =yes=, check the authority (typically the password) and if all
Chris@0 701 % looks good redirect the browser to ReturnTo, adding the OpenID
Chris@0 702 % properties needed by the Relying Party to verify the login.
Chris@0 703
Chris@0 704 openid_grant(Request) :-
Chris@0 705 http_parameters(Request,
Chris@0 706 [ 'openid.grant'(Grant),
Chris@0 707 'openid.return_to'(ReturnTo)
Chris@0 708 ],
Chris@0 709 [ attribute_declarations(openid_attribute)
Chris@0 710 ]),
Chris@0 711 ( Grant == yes
Chris@0 712 -> http_parameters(Request,
Chris@0 713 [ 'openid.identity'(Identity),
Chris@0 714 'openid.assoc_handle'(Handle),
Chris@0 715 'openid.trust_root'(TrustRoot),
Chris@0 716 'openid.password'(Password)
Chris@0 717 ],
Chris@0 718 [ attribute_declarations(openid_attribute)
Chris@0 719 ]),
Chris@0 720 server_association(_, Handle, Association),
Chris@0 721 grant_login(Request,
Chris@0 722 [ identity(Identity),
Chris@0 723 password(Password),
Chris@0 724 trustroot(TrustRoot)
Chris@0 725 ]),
Chris@0 726 SignedPairs = [ 'mode'-id_res,
Chris@0 727 'identity'-Identity,
Chris@0 728 'assoc_handle'-Handle,
Chris@0 729 'return_to'-ReturnTo
Chris@0 730 ],
Chris@0 731 signed_fields(SignedPairs, Signed),
Chris@0 732 signature(SignedPairs, Association, Signature),
Chris@0 733 phrase(base64(Signature), Bas64Sig),
Chris@0 734 redirect_browser(ReturnTo,
Chris@0 735 [ 'openid.mode' = id_res,
Chris@0 736 'openid.identity' = Identity,
Chris@0 737 'openid.assoc_handle' = Handle,
Chris@0 738 'openid.return_to' = ReturnTo,
Chris@0 739 'openid.signed' = Signed,
Chris@0 740 'openid.sig' = Bas64Sig
Chris@0 741 ])
Chris@0 742 ; redirect_browser(ReturnTo,
Chris@0 743 [ 'openid.mode' = cancel
Chris@0 744 ])
Chris@0 745 ).
Chris@0 746
Chris@0 747
Chris@0 748 %% grant_login(+Request, +Options) is det.
Chris@0 749 %
Chris@0 750 % Validate login from Request (can be used to get cookies) and
Chris@0 751 % Options, which contains at least:
Chris@0 752 %
Chris@0 753 % * identity(Identity)
Chris@0 754 % * password(Password)
Chris@0 755 % * trustroot(TrustRoot)
Chris@0 756
Chris@0 757 grant_login(Request, Options) :-
Chris@0 758 openid_hook(grant(Request, Options)).
Chris@0 759
Chris@0 760 %% trusted(+OpenID, +Server)
Chris@0 761 %
Chris@0 762 % True if we trust the given OpenID server. Must throw an
Chris@0 763 % exception, possibly redirecting to a page with trusted servers
Chris@0 764 % if the given server is not trusted.
Chris@0 765 %
Chris@0 766 % @tbd How do we manage this? Broadcast? Settings? Hook?
Chris@0 767
Chris@0 768 trusted(OpenID, Server) :-
Chris@0 769 openid_hook(trusted(OpenID, Server)).
Chris@0 770
Chris@0 771
Chris@0 772 %% signed_fields(+Pairs, -Signed) is det.
Chris@0 773 %
Chris@0 774 % Create a comma-separated atom from the field-names without
Chris@0 775 % 'openid.' from Pairs.
Chris@0 776
Chris@0 777 signed_fields(Pairs, Signed) :-
Chris@0 778 signed_field_names(Pairs, Names),
Chris@0 779 concat_atom(Names, ',', Signed).
Chris@0 780
Chris@0 781 signed_field_names([], []).
Chris@0 782 signed_field_names([H0-_|T0], [H|T]) :-
Chris@0 783 ( atom_concat('openid.', H, H0)
Chris@0 784 -> true
Chris@0 785 ; H = H0
Chris@0 786 ),
Chris@0 787 signed_field_names(T0, T).
Chris@0 788
Chris@0 789 %% signature(+Pairs, +Association, -Signature)
Chris@0 790 %
Chris@0 791 % Determine the signature for Pairs
Chris@0 792
Chris@0 793 signature(Pairs, Association, Signature) :-
Chris@0 794 key_values_data(Pairs, TokenContents),
Chris@0 795 association_mac_key(Association, MacKey),
Chris@0 796 association_session_type(Association, SessionType),
Chris@0 797 signature_algorithm(SessionType, SHA),
Chris@0 798 hmac_sha(MacKey, TokenContents, Signature, [algorithm(SHA)]),
Chris@0 799 debug(openid(crypt), 'Signed:~n~s~nSignature: ~w', [TokenContents, Signature]).
Chris@0 800
Chris@0 801 signature_algorithm('DH-SHA1', sha1).
Chris@0 802 signature_algorithm('DH-SHA256', sha256).
Chris@0 803
Chris@0 804
Chris@0 805 /*******************************
Chris@0 806 * IMAGES *
Chris@0 807 *******************************/
Chris@0 808
Chris@0 809 %% openid_file(+Request)
Chris@0 810 %
Chris@0 811 % Serve fiels we use as logos, style-sheets, etc.
Chris@0 812
Chris@0 813 openid_file(Request) :-
Chris@0 814 http_parameters(Request,
Chris@0 815 [ name(Name, [])
Chris@0 816 ]),
Chris@0 817 image_file(Name, File),
Chris@0 818 http_reply_file(File, [], Request).
Chris@0 819
Chris@0 820
Chris@0 821 image_file(openid_logo, library('http/openid-logo-square.png')).
Chris@0 822 image_file(openid_logo_tiny, library('http/openid-logo-tiny.png')).
Chris@0 823 image_file(openid_css, library('http/openid.css')).
Chris@0 824
Chris@0 825
Chris@0 826 /*******************************
Chris@0 827 * ASSOCIATE *
Chris@0 828 *******************************/
Chris@0 829
Chris@0 830 :- dynamic
Chris@0 831 association/3. % URL, Handle, Data
Chris@0 832
Chris@0 833 :- record
Chris@0 834 association(session_type='DH-SHA1',
Chris@0 835 expires_at, % time-stamp
Chris@0 836 mac_key). % code-list
Chris@0 837
Chris@0 838 %% openid_associate(+URL, -Handle, -Assoc) is det.
Chris@0 839 %% openid_associate(?URL, +Handle, -Assoc) is semidet.
Chris@0 840 %
Chris@0 841 % Associate with an open-id server. We first check for a still
Chris@0 842 % valid old association. If there is none or it is expired, we
Chris@0 843 % esstablish one and remember it.
Chris@0 844 %
Chris@0 845 % @tbd Should we store known associations permanently? Where?
Chris@0 846
Chris@0 847 openid_associate(URL, Handle, Assoc) :-
Chris@0 848 association(URL, Handle, Assoc),
Chris@0 849 association_expires_at(Assoc, Expires),
Chris@0 850 get_time(Now),
Chris@0 851 ( Now < Expires
Chris@0 852 -> debug(openid(associate), '~w: Reusing association', [URL])
Chris@0 853 ; retractall(association(URL, Handle, _)),
Chris@0 854 fail
Chris@0 855 ).
Chris@0 856 openid_associate(URL, Handle, Assoc) :-
Chris@0 857 ground(URL),
Chris@0 858 associate_data(Data, P, _G, X),
Chris@0 859 http_post(URL, form(Data), Reply, [to(codes)]),
Chris@0 860 debug(openid(associate), 'Reply: ~n~s', [Reply]),
Chris@0 861 key_values_data(Pairs, Reply),
Chris@0 862 shared_secret(Pairs, P, X, MacKey),
Chris@0 863 expires_at(Pairs, ExpiresAt),
Chris@0 864 memberchk(assoc_handle-Handle, Pairs),
Chris@0 865 memberchk(session_type-Type, Pairs),
Chris@0 866 make_association([ session_type(Type),
Chris@0 867 expires_at(ExpiresAt),
Chris@0 868 mac_key(MacKey)
Chris@0 869 ], Assoc),
Chris@0 870 assert(association(URL, Handle, Assoc)).
Chris@0 871
Chris@0 872
Chris@0 873 %% shared_secret(+Pairs, +P, +X, -Secret:list(codes))
Chris@0 874 %
Chris@0 875 % Find the shared secret from the peer's reply and our data. First
Chris@0 876 % clause deals with the (deprecated) non-encoded version.
Chris@0 877
Chris@0 878 shared_secret(Pairs, _, _, Secret) :-
Chris@0 879 memberchk(mac_key-Base64, Pairs), !,
Chris@0 880 atom_codes(Base64, Base64Codes),
Chris@0 881 phrase(base64(Base64Codes), Secret).
Chris@0 882 shared_secret(Pairs, P, X, Secret) :-
Chris@0 883 memberchk(dh_server_public-Base64Public, Pairs),
Chris@0 884 memberchk(enc_mac_key-Base64EncMacKey, Pairs),
Chris@0 885 base64_btwoc(ServerPublic, Base64Public),
Chris@0 886 DiffieHellman is powm(ServerPublic, X, P),
Chris@0 887 atom_codes(Base64EncMacKey, Base64EncMacKeyCodes),
Chris@0 888 phrase(base64(EncMacKey), Base64EncMacKeyCodes),
Chris@0 889 btwoc(DiffieHellman, DiffieHellmanBytes),
Chris@0 890 sha_hash(DiffieHellmanBytes, DHHash, [algorithm(sha1)]),
Chris@0 891 xor_codes(DHHash, EncMacKey, Secret).
Chris@0 892
Chris@0 893
Chris@0 894 %% expires_at(+Pairs, -Time) is det.
Chris@0 895 %
Chris@0 896 % Unify Time with a time-stamp stating when the association
Chris@0 897 % exires.
Chris@0 898
Chris@0 899 expires_at(Pairs, Time) :-
Chris@0 900 memberchk(expires_in-ExpAtom, Pairs),
Chris@0 901 atom_number(ExpAtom, Seconds),
Chris@0 902 get_time(Now),
Chris@0 903 Time is integer(Now)+Seconds.
Chris@0 904
Chris@0 905
Chris@0 906 %% associate_data(-Data, -X) is det.
Chris@0 907 %
Chris@0 908 % Generate the data to initiate an association using Diffie-Hellman
Chris@0 909 % shared secret key negotiation.
Chris@0 910
Chris@0 911 associate_data(Data, P, G, X) :-
Chris@0 912 openid_dh_p(P),
Chris@0 913 openid_dh_g(G),
Chris@0 914 dh_x(P, X),
Chris@0 915 CP is powm(G, X, P),
Chris@0 916 base64_btwoc(P, P64),
Chris@0 917 base64_btwoc(G, G64),
Chris@0 918 base64_btwoc(CP, CP64),
Chris@0 919 Data = [ 'openid.mode' = associate,
Chris@0 920 'openid.assoc_type' = 'HMAC-SHA1',
Chris@0 921 'openid.session_type' = 'DH-SHA1',
Chris@0 922 'openid.dh_modulus' = P64,
Chris@0 923 'openid.dh_gen' = G64,
Chris@0 924 'openid.dh_consumer_public' = CP64
Chris@0 925 ].
Chris@0 926
Chris@0 927
Chris@0 928 /*******************************
Chris@0 929 * RANDOM *
Chris@0 930 *******************************/
Chris@0 931
Chris@0 932 %% random_bytes(+N, -Bytes) is det.
Chris@0 933 %
Chris@0 934 % Bytes is a list of N random bytes (integers 0..255).
Chris@0 935
Chris@0 936 random_bytes(N, [H|T]) :-
Chris@0 937 N > 0, !,
Chris@0 938 H is random(256),
Chris@0 939 N2 is N - 1,
Chris@0 940 random_bytes(N2, T).
Chris@0 941 random_bytes(_, []).
Chris@0 942
Chris@0 943
Chris@0 944 %% dh_x(+Max, -X)
Chris@0 945 %
Chris@0 946 % Generate a random key X where 1<=X<P-1)
Chris@0 947 %
Chris@0 948 % @tbd If we have /dev/urandom, use that.
Chris@0 949
Chris@0 950 dh_x(P, X) :-
Chris@0 951 X0 is random(65536),
Chris@0 952 Max is P - 1,
Chris@0 953 dh_x(Max, X0, X).
Chris@0 954
Chris@0 955 dh_x(Max, X0, X) :-
Chris@0 956 X1 is X0<<16+random(65536),
Chris@0 957 ( X1 >= Max
Chris@0 958 -> X = X0
Chris@0 959 ; dh_x(Max, X1, X)
Chris@0 960 ).
Chris@0 961
Chris@0 962
Chris@0 963 /*******************************
Chris@0 964 * CONSTANTS *
Chris@0 965 *******************************/
Chris@0 966
Chris@0 967 openid_dh_p(155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443).
Chris@0 968
Chris@0 969 openid_dh_g(2).
Chris@0 970
Chris@0 971
Chris@0 972 /*******************************
Chris@0 973 * UTIL *
Chris@0 974 *******************************/
Chris@0 975
Chris@0 976 %% key_values_data(+KeyValues:list(Key-Value), -Data:list(code)) is det.
Chris@0 977 %% key_values_data(-KeyValues:list(Key-Value), +Data:list(code)) is det.
Chris@0 978 %
Chris@0 979 % Encoding and decoding of key-value pairs for OpenID POST
Chris@0 980 % messages according to Appendix C of the OpenID 1.1
Chris@0 981 % specification.
Chris@0 982
Chris@0 983 key_values_data(Pairs, Data) :-
Chris@0 984 nonvar(Data), !,
Chris@0 985 phrase(data_form(Pairs), Data).
Chris@0 986 key_values_data(Pairs, Data) :-
Chris@0 987 phrase(gen_data_form(Pairs), Data).
Chris@0 988
Chris@0 989 data_form([Key-Value|Pairs]) -->
Chris@0 990 utf8_string(KeyCodes), ":", utf8_string(ValueCodes), "\n", !,
Chris@0 991 { atom_codes(Key, KeyCodes),
Chris@0 992 atom_codes(Value, ValueCodes)
Chris@0 993 },
Chris@0 994 data_form(Pairs).
Chris@0 995 data_form([]) -->
Chris@0 996 ws.
Chris@0 997
Chris@0 998 %% utf8_string(-Codes)// is nondet.
Chris@0 999 %
Chris@0 1000 % Take a short UTF-8 code-list from input. Extend on backtracking.
Chris@0 1001
Chris@0 1002 utf8_string([]) -->
Chris@0 1003 [].
Chris@0 1004 utf8_string([H|T]) -->
Chris@0 1005 utf8_codes([H]),
Chris@0 1006 utf8_string(T).
Chris@0 1007
Chris@0 1008 ws -->
Chris@0 1009 [C],
Chris@0 1010 { C =< 32 }, !,
Chris@0 1011 ws.
Chris@0 1012 ws -->
Chris@0 1013 [].
Chris@0 1014
Chris@0 1015
Chris@0 1016 gen_data_form([]) -->
Chris@0 1017 [].
Chris@0 1018 gen_data_form([Key-Value|T]) -->
Chris@0 1019 field(Key), ":", field(Value), "\n",
Chris@0 1020 gen_data_form(T).
Chris@0 1021
Chris@0 1022 field(Field) -->
Chris@0 1023 { to_codes(Field, Codes)
Chris@0 1024 },
Chris@0 1025 utf8_codes(Codes).
Chris@0 1026
Chris@0 1027 to_codes(Codes, Codes) :-
Chris@0 1028 is_list(Codes), !.
Chris@0 1029 to_codes(Atomic, Codes) :-
Chris@0 1030 atom_codes(Atomic, Codes).
Chris@0 1031
Chris@0 1032 %% base64_btwoc(+Int, -Base64:list(code)) is det.
Chris@0 1033 %% base64_btwoc(-Int, +Base64:list(code)) is det.
Chris@0 1034 %% base64_btwoc(-Int, +Base64:atom) is det.
Chris@0 1035
Chris@0 1036 base64_btwoc(Int, Base64) :-
Chris@0 1037 integer(Int), !,
Chris@0 1038 btwoc(Int, Bytes),
Chris@0 1039 phrase(base64(Bytes), Base64).
Chris@0 1040 base64_btwoc(Int, Base64) :-
Chris@0 1041 atom(Base64), !,
Chris@0 1042 atom_codes(Base64, Codes),
Chris@0 1043 phrase(base64(Bytes), Codes),
Chris@0 1044 btwoc(Int, Bytes).
Chris@0 1045 base64_btwoc(Int, Base64) :-
Chris@0 1046 phrase(base64(Bytes), Base64),
Chris@0 1047 btwoc(Int, Bytes).
Chris@0 1048
Chris@0 1049
Chris@0 1050 %% btwoc(+Integer, -Bytes) is det.
Chris@0 1051 %% btwoc(-Integer, +Bytes) is det.
Chris@0 1052 %
Chris@0 1053 % Translate between a big integer and and its representation in
Chris@0 1054 % bytes. The first bit is always 0, as Integer is nonneg.
Chris@0 1055
Chris@0 1056 btwoc(Int, Bytes) :-
Chris@0 1057 integer(Int), !,
Chris@0 1058 int_to_bytes(Int, Bytes).
Chris@0 1059 btwoc(Int, Bytes) :-
Chris@0 1060 is_list(Bytes),
Chris@0 1061 bytes_to_int(Bytes, Int).
Chris@0 1062
Chris@0 1063 int_to_bytes(Int, Bytes) :-
Chris@0 1064 int_to_bytes(Int, [], Bytes).
Chris@0 1065
Chris@0 1066 int_to_bytes(Int, Bytes0, [Int|Bytes0]) :-
Chris@0 1067 Int < 128, !.
Chris@0 1068 int_to_bytes(Int, Bytes0, Bytes) :-
Chris@0 1069 Last is Int /\ 0xff,
Chris@0 1070 Int1 is Int >> 8,
Chris@0 1071 int_to_bytes(Int1, [Last|Bytes0], Bytes).
Chris@0 1072
Chris@0 1073
Chris@0 1074 bytes_to_int([B|T], Int) :-
Chris@0 1075 bytes_to_int(T, B, Int).
Chris@0 1076
Chris@0 1077 bytes_to_int([], Int, Int).
Chris@0 1078 bytes_to_int([B|T], Int0, Int) :-
Chris@0 1079 Int1 is (Int0<<8)+B,
Chris@0 1080 bytes_to_int(T, Int1, Int).
Chris@0 1081
Chris@0 1082
Chris@0 1083 %% xor_codes(+C1:list(int), +C2:list(int), -XOR:list(int)) is det.
Chris@0 1084 %
Chris@0 1085 % Compute xor of two strings.
Chris@0 1086 %
Chris@0 1087 % @error length_mismatch(L1, L2) if the two lists do not have equal
Chris@0 1088 % length.
Chris@0 1089
Chris@0 1090 xor_codes([], [], []).
Chris@0 1091 xor_codes([H1|T1], [H2|T2], [H|T]) :-
Chris@0 1092 H is H1 xor H2, !,
Chris@0 1093 xor_codes(T1, T2, T).
Chris@0 1094 xor_codes(L1, L2, _) :-
Chris@0 1095 throw(error(length_mismatch(L1, L2), _)).
Chris@0 1096
Chris@0 1097
Chris@0 1098 /*******************************
Chris@0 1099 * HTTP ATTRIBUTES *
Chris@0 1100 *******************************/
Chris@0 1101
Chris@0 1102 openid_attribute('openid.mode',
Chris@0 1103 [ oneof([ associate,
Chris@0 1104 checkid_setup,
Chris@0 1105 cancel,
Chris@0 1106 id_res
Chris@0 1107 ])
Chris@0 1108 ]).
Chris@0 1109 openid_attribute('openid.assoc_type',
Chris@0 1110 [ oneof(['HMAC-SHA1'])
Chris@0 1111 ]).
Chris@0 1112 openid_attribute('openid.session_type',
Chris@0 1113 [ oneof([ 'DH-SHA1',
Chris@0 1114 'DH-SHA256'
Chris@0 1115 ])
Chris@0 1116 ]).
Chris@0 1117 openid_attribute('openid.dh_modulus', [length > 1]).
Chris@0 1118 openid_attribute('openid.dh_gen', [length > 1]).
Chris@0 1119 openid_attribute('openid.dh_consumer_public', [length > 1]).
Chris@0 1120 openid_attribute('openid.assoc_handle', [length > 1]).
Chris@0 1121 openid_attribute('openid.return_to', [length > 1]).
Chris@0 1122 openid_attribute('openid.trust_root', [length > 1]).
Chris@0 1123 openid_attribute('openid.identity', [length > 1]).
Chris@0 1124 openid_attribute('openid.password', [length > 1]).
Chris@0 1125 openid_attribute('openid.grant', [oneof([yes,no])]).