annotate jamendo/sparql-archived/SeRQL/user_db.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@scienc.uva.nl
Chris@0 7 WWW: http://www.swi-prolog.org
Chris@0 8 Copyright (C): 1985-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(user_db,
Chris@0 33 [ set_user_database/1, % +File
Chris@0 34
Chris@0 35 user_add/2, % +Name, +Properties
Chris@0 36 user_del/1, % +Name,
Chris@0 37 set_user_property/2, % +Name, +Property
Chris@0 38
Chris@0 39 openid_add_server/2, % +Server, +Options
Chris@0 40 openid_del_server/1, % +Server
Chris@0 41 openid_set_property/2, % +Server, +Property
Chris@0 42 openid_current_server/1, % ?Server
Chris@0 43 openid_server_property/2, % ?Server, ?Property
Chris@0 44 openid_server_properties/2, % ?Server, ?Property
Chris@0 45
Chris@0 46 user_property/2, % ?Name, ?Property
Chris@0 47 check_permission/2, % +User, +Operation
Chris@0 48 validate_password/2, % +User, +Password
Chris@0 49 password_hash/2, % +Password, ?Hash
Chris@0 50
Chris@0 51 login/1, % +User
Chris@0 52 logout/1, % +User
Chris@0 53 current_user/1, % ?User
Chris@0 54 logged_on/1, % -User
Chris@0 55 ensure_logged_on/1, % -User
Chris@0 56 authorized/1, % +Action
Chris@0 57
Chris@0 58 deny_all_users/1 % +What
Chris@0 59 ]).
Chris@0 60 :- use_module(library('semweb/rdf_db')).
Chris@0 61 :- use_module(library('http/http_session')).
Chris@0 62 :- use_module(library('http/http_wrapper')).
Chris@0 63 :- use_module(library('http/http_openid')).
Chris@0 64 :- use_module(library(lists)).
Chris@0 65 :- use_module(library(settings)).
Chris@0 66 :- use_module(library(error)).
Chris@0 67 :- use_module(library(url)).
Chris@0 68 :- use_module(openid).
Chris@0 69 :- use_module(db).
Chris@0 70
Chris@0 71 /** <module> User administration
Chris@0 72
Chris@0 73 Core user administration. The user administration is based on the
Chris@0 74 following:
Chris@0 75
Chris@0 76 * A persistent fact user/2
Chris@0 77 * A dynamic fact logged_in/3
Chris@0 78 * Session management
Chris@0 79
Chris@0 80 @tbd Consider using the RDF database for login. Maybe requires
Chris@0 81 multiple RDF databases?
Chris@0 82
Chris@0 83 @author Jan Wielemaker
Chris@0 84 */
Chris@0 85
Chris@0 86 :- dynamic
Chris@0 87 logged_in/3, % Session, User, Time
Chris@0 88 user/2, % Name, Options
Chris@0 89 denied/1. % Deny to all users
Chris@0 90
Chris@0 91
Chris@0 92 /*******************************
Chris@0 93 * USER DATABASE *
Chris@0 94 *******************************/
Chris@0 95
Chris@0 96 :- db_term
Chris@0 97 user(_Name, _UserOptions),
Chris@0 98 grant_openid_server(_Server, _ServerOptions).
Chris@0 99
Chris@0 100 %% set_user_database(+File) is det.
Chris@0 101 %
Chris@0 102 % Load user/2 from File. Changes are fully synchronous.
Chris@0 103
Chris@0 104 set_user_database(File) :-
Chris@0 105 db_attach(File, [sync(close)]).
Chris@0 106
Chris@0 107 %% user_add(+Name, +Properties) is det.
Chris@0 108 %
Chris@0 109 % Add a new user with given properties.
Chris@0 110
Chris@0 111 user_add(Name, Options) :-
Chris@0 112 must_be(atom, Name),
Chris@0 113 db_assert(user(Name, Options)).
Chris@0 114
Chris@0 115 %% user_del(+Name)
Chris@0 116 %
Chris@0 117 % Delete named user from user-database.
Chris@0 118
Chris@0 119 user_del(Name) :-
Chris@0 120 must_be(atom, Name),
Chris@0 121 ( user(Name, _)
Chris@0 122 -> db_retractall(user(Name, _))
Chris@0 123 ; existence_error(user, Name)
Chris@0 124 ).
Chris@0 125
Chris@0 126 %% set_user_property(+Name, +Property) is det.
Chris@0 127 %
Chris@0 128 % Replace Property for user Name.
Chris@0 129
Chris@0 130 set_user_property(Name, Prop) :-
Chris@0 131 must_be(atom, Name),
Chris@0 132 ( user(Name, OldProps)
Chris@0 133 -> ( memberchk(Prop, OldProps)
Chris@0 134 -> true
Chris@0 135 ; functor(Prop, PropName, Arity),
Chris@0 136 functor(Unbound, PropName, Arity),
Chris@0 137 delete(OldProps, Unbound, NewProps),
Chris@0 138 db_retractall(user(Name, _)),
Chris@0 139 db_assert(user(Name, [Prop|NewProps]))
Chris@0 140 )
Chris@0 141 ; existence_error(user, Name)
Chris@0 142 ).
Chris@0 143
Chris@0 144
Chris@0 145 %% openid_add_server(+Server, +Options)
Chris@0 146 %
Chris@0 147 % Register an OpenID server.
Chris@0 148
Chris@0 149 openid_add_server(Server, _Options) :-
Chris@0 150 openid_current_server(Server), !,
Chris@0 151 throw(error(permission_error(create, openid_server, Server),
Chris@0 152 context(_, 'Already present'))).
Chris@0 153 openid_add_server(Server, Options) :-
Chris@0 154 db_assert(grant_openid_server(Server, Options)).
Chris@0 155
Chris@0 156
Chris@0 157 %% openid_del_server(+Server)
Chris@0 158 %
Chris@0 159 % Delete registration of an OpenID server.
Chris@0 160
Chris@0 161 openid_del_server(Server) :-
Chris@0 162 db_retractall(grant_openid_server(Server, _)).
Chris@0 163
Chris@0 164
Chris@0 165 %% openid_set_property(+Server, +Property) is det.
Chris@0 166 %
Chris@0 167 % Replace Property for OpenID Server
Chris@0 168
Chris@0 169 openid_set_property(Server, Prop) :-
Chris@0 170 must_be(atom, Server),
Chris@0 171 ( grant_openid_server(Server, OldProps)
Chris@0 172 -> ( memberchk(Prop, OldProps)
Chris@0 173 -> true
Chris@0 174 ; functor(Prop, PropName, Arity),
Chris@0 175 functor(Unbound, PropName, Arity),
Chris@0 176 delete(OldProps, Unbound, NewProps),
Chris@0 177 db_retractall(grant_openid_server(Server, _)),
Chris@0 178 db_assert(grant_openid_server(Server, [Prop|NewProps]))
Chris@0 179 )
Chris@0 180 ; existence_error(openid_server, Server)
Chris@0 181 ).
Chris@0 182
Chris@0 183
Chris@0 184 %% openid_current_server(?Server) is nondet.
Chris@0 185 %
Chris@0 186
Chris@0 187 openid_current_server(Server) :-
Chris@0 188 grant_openid_server(Server, _).
Chris@0 189
Chris@0 190 %% openid_server_properties(+Server, -Properties) is semidet.
Chris@0 191 %
Chris@0 192 % Try find properties for the given server. Note that we generally
Chris@0 193 % refer to a server using its domain. The actjual server may be a
Chris@0 194 % path on the server or a machine in the domain.
Chris@0 195
Chris@0 196 :- dynamic
Chris@0 197 registered_server/2.
Chris@0 198
Chris@0 199 openid_server_properties(Server, Properties) :-
Chris@0 200 ( registered_server(Server, Registered)
Chris@0 201 -> grant_openid_server(Registered, Properties)
Chris@0 202 ; grant_openid_server(Server, Properties)
Chris@0 203 -> true
Chris@0 204 ; grant_openid_server(Registered, Properties),
Chris@0 205 match_server(Server, Registered)
Chris@0 206 -> assert(registered_server(Server, Registered))
Chris@0 207 ; grant_openid_server(*, Properties)
Chris@0 208 ).
Chris@0 209
Chris@0 210 %% match_server(+ServerURL, +RegisteredURL) is semidet.
Chris@0 211 %
Chris@0 212 % True if ServerURL is in the domain of RegisteredURL.
Chris@0 213
Chris@0 214 match_server(Server, Registered) :-
Chris@0 215 parse_url(Server, SParts),
Chris@0 216 memberchk(host(SHost), SParts),
Chris@0 217 parse_url(Registered, RParts),
Chris@0 218 memberchk(host(RHost), RParts),
Chris@0 219 concat_atom(SL, '.', SHost),
Chris@0 220 concat_atom(RL, '.', RHost),
Chris@0 221 append(_, RL, SL), !.
Chris@0 222
Chris@0 223
Chris@0 224 openid_server_property(Server, Property) :-
Chris@0 225 openid_server_properties(Server, Properties),
Chris@0 226 ( var(Property)
Chris@0 227 -> member(Property, Properties)
Chris@0 228 ; memberchk(Property, Properties)
Chris@0 229 ).
Chris@0 230
Chris@0 231
Chris@0 232 /*******************************
Chris@0 233 * USER QUERY *
Chris@0 234 *******************************/
Chris@0 235
Chris@0 236 %% current_user(?User)
Chris@0 237 %
Chris@0 238 % True if User is a registered user.
Chris@0 239
Chris@0 240 current_user(User) :-
Chris@0 241 user(User, _).
Chris@0 242
Chris@0 243 %% user_property(?User, ?Property) is nondet.
Chris@0 244 %% user_property(+User, +Property) is semidet.
Chris@0 245 %
Chris@0 246 % True if Property is a defined property on User. In addition to
Chris@0 247 % properties explicitely stored with users, we define:
Chris@0 248 %
Chris@0 249 % * session(SessionID)
Chris@0 250 % * connection(LoginTime, Idle)
Chris@0 251 % * url(URL)
Chris@0 252 % Generates reference to our own OpenID server for local
Chris@0 253 % login
Chris@0 254 % * openid(OpenID)
Chris@0 255 % Refers to the official OpenID (possibly delegated)
Chris@0 256 % * openid_server(Server)
Chris@0 257 % Refers to the OpenID server that validated the login
Chris@0 258
Chris@0 259 user_property(User, Property) :-
Chris@0 260 nonvar(User), nonvar(Property), !,
Chris@0 261 uprop(Property, User), !.
Chris@0 262 user_property(User, Property) :-
Chris@0 263 uprop(Property, User).
Chris@0 264
Chris@0 265 uprop(session(SessionID), User) :-
Chris@0 266 ( nonvar(SessionID) % speedup
Chris@0 267 -> !
Chris@0 268 ; true
Chris@0 269 ),
Chris@0 270 logged_in(SessionID, User, _).
Chris@0 271 uprop(connection(LoginTime, Idle), User) :-
Chris@0 272 logged_in(SessionID, User, LoginTime),
Chris@0 273 http_current_session(SessionID, idle(Idle)).
Chris@0 274 uprop(url(URL), User) :-
Chris@0 275 user_url(User, URL).
Chris@0 276 uprop(Prop, User) :-
Chris@0 277 nonvar(User), !,
Chris@0 278 ( user(User, Properties)
Chris@0 279 -> true
Chris@0 280 ; openid_server(User, OpenID, Server),
Chris@0 281 openid_server_properties(Server, Properties0)
Chris@0 282 -> Properties = [type(openid),openid(OpenID),openid_server(Server)|Properties0]
Chris@0 283 ),
Chris@0 284 ( nonvar(Prop)
Chris@0 285 -> memberchk(Prop, Properties)
Chris@0 286 ; member(Prop, Properties)
Chris@0 287 ).
Chris@0 288 uprop(Prop, User) :-
Chris@0 289 user(User, Properties),
Chris@0 290 member(Prop, Properties).
Chris@0 291
Chris@0 292
Chris@0 293 user_url(User, URL) :-
Chris@0 294 is_absolute_url(User), !,
Chris@0 295 URL = User.
Chris@0 296 user_url(User, URL) :-
Chris@0 297 openid_for_local_user(User, URL).
Chris@0 298
Chris@0 299
Chris@0 300 /*******************************
Chris@0 301 * MISC ROUTINES *
Chris@0 302 *******************************/
Chris@0 303
Chris@0 304 %% validate_password(+User, +Password)
Chris@0 305 %
Chris@0 306 % Validate the password for the given user and password.
Chris@0 307
Chris@0 308 validate_password(User, Password) :-
Chris@0 309 user(User, Options),
Chris@0 310 memberchk(password(Hash), Options),
Chris@0 311 password_hash(Password, Hash).
Chris@0 312
Chris@0 313
Chris@0 314 %% password_hash(+Password, ?Hash)
Chris@0 315 %
Chris@0 316 % Generate a hash from a password or test a password against a
Chris@0 317 % hash. Like Unix we add a random 2 character prefix to make the
Chris@0 318 % same password return different hashes and thus obscure equal
Chris@0 319 % passwords.
Chris@0 320 %
Chris@0 321 % @tbd Use crypt/2 from library(crypt)
Chris@0 322
Chris@0 323 password_hash(Password, Hash) :-
Chris@0 324 var(Hash), !,
Chris@0 325 C1 is random(0'z-0'a) + 0'a,
Chris@0 326 C2 is random(0'z-0'a) + 0'a,
Chris@0 327 atom_codes(Password, Codes),
Chris@0 328 rdf_atom_md5([C1,C2|Codes], 100000, Hash0),
Chris@0 329 atom_codes(Prefix, [C1, C2]),
Chris@0 330 atom_concat(Prefix, Hash0, Hash).
Chris@0 331 password_hash(Password, Hash) :-
Chris@0 332 sub_atom(Hash, 0, 2, _, Prefix),
Chris@0 333 sub_atom(Hash, 2, _, 0, Hash0),
Chris@0 334 atom_codes(Prefix, [C1, C2]),
Chris@0 335 atom_codes(Password, Codes),
Chris@0 336 rdf_atom_md5([C1,C2|Codes], 100000, Hash0).
Chris@0 337
Chris@0 338
Chris@0 339 /*******************************
Chris@0 340 * LOGIN/PERMISSIONS *
Chris@0 341 *******************************/
Chris@0 342
Chris@0 343 %% logged_on(-User) is det.
Chris@0 344 %
Chris@0 345 % True if User is the name of the currently logged in user.
Chris@0 346 %
Chris@0 347 % @error context_error(not_logged_in)
Chris@0 348
Chris@0 349 logged_on(User) :-
Chris@0 350 http_session_id(SessionID),
Chris@0 351 user_property(User, session(SessionID)), !.
Chris@0 352 logged_on(_) :-
Chris@0 353 throw(error(context_error(not_logged_in), _)).
Chris@0 354
Chris@0 355 %% ensure_logged_on(-User)
Chris@0 356 %
Chris@0 357 % Make sure we are logged in and return the current user.
Chris@0 358 % See openid_user/3 for details.
Chris@0 359
Chris@0 360 ensure_logged_on(User) :-
Chris@0 361 http_current_request(Request),
Chris@0 362 ( catch(setting(http:prefix, Prefix), _, fail)
Chris@0 363 -> atom_concat(Prefix, '/openid/login', LoginURL),
Chris@0 364 openid_user(Request, User, [login_url(LoginURL)])
Chris@0 365 ; openid_user(Request, User, [])
Chris@0 366 ).
Chris@0 367
Chris@0 368
Chris@0 369 %% authorized(+Action) is det.
Chris@0 370 %
Chris@0 371 % validate the current user is allowed to perform Action. Throws
Chris@0 372 % a permission error if this is not the case. Never fails.
Chris@0 373 %
Chris@0 374 % @error permission_error(http_location, access, Path)
Chris@0 375
Chris@0 376 authorized(Action) :-
Chris@0 377 catch(check_permission(anonymous, Action), _, fail), !.
Chris@0 378 authorized(Action) :-
Chris@0 379 ensure_logged_on(User),
Chris@0 380 check_permission(User, Action).
Chris@0 381
Chris@0 382
Chris@0 383 %% check_permission(+User, +Operation)
Chris@0 384 %
Chris@0 385 % Validate that user is allowed to perform Operation.
Chris@0 386 %
Chris@0 387 % @error permission_error(http_location, access, Path)
Chris@0 388
Chris@0 389 check_permission(User, Operation) :-
Chris@0 390 \+ denied(User, Operation),
Chris@0 391 user_property(User, allow(Operations)),
Chris@0 392 memberchk(Operation, Operations), !.
Chris@0 393 check_permission(_, _) :-
Chris@0 394 http_current_request(Request),
Chris@0 395 memberchk(path(Path), Request),
Chris@0 396 permission_error(http_location, access, Path).
Chris@0 397
Chris@0 398 %% denied(+User, +Operation)
Chris@0 399 %
Chris@0 400 % Deny actions to all users but admin. This is a bit of a quick
Chris@0 401 % hack to avoid loosing data in a multi-user experiment. Do not
Chris@0 402 % yet rely on this,
Chris@0 403
Chris@0 404 denied(admin, _) :- !, fail.
Chris@0 405 denied(_, Operation) :-
Chris@0 406 denied(Operation).
Chris@0 407
Chris@0 408
Chris@0 409 %% deny_all_users(+Term)
Chris@0 410 %
Chris@0 411 % Deny some action to all users. See above.
Chris@0 412
Chris@0 413 deny_all_users(Term) :-
Chris@0 414 ( denied(X),
Chris@0 415 X =@= Term
Chris@0 416 -> true
Chris@0 417 ; assert(denied(Term))
Chris@0 418 ).
Chris@0 419
Chris@0 420
Chris@0 421 %% login(+User:atom)
Chris@0 422 %
Chris@0 423 % Accept user as a user that has logged on into the current
Chris@0 424 % session.
Chris@0 425
Chris@0 426 login(User) :-
Chris@0 427 must_be(atom, User),
Chris@0 428 get_time(Time),
Chris@0 429 http_session_id(Session),
Chris@0 430 retractall(logged_in(_, Session, _)),
Chris@0 431 assert(logged_in(Session, User, Time)).
Chris@0 432
Chris@0 433 %% logout(+User)
Chris@0 434 %
Chris@0 435 % Logout the specified user
Chris@0 436
Chris@0 437 logout(User) :-
Chris@0 438 must_be(atom, User),
Chris@0 439 retractall(logged_in(_Session, User, _Time)).