1/* Part of SWISH 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@cs.vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (C): 2017, VU University Amsterdam 7 CWI Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(swish_authenticate, 37 [ authenticate/2, % +Request, -Authentity 38 user_property/2 % +Authentity, ?Property 39 ]). 40:- use_module(library(http/http_wrapper)). 41:- use_module(library(debug)). 42:- use_module(library(broadcast)). 43 44:- use_module(config). 45 46/** <module> Authentication access for SWISH 47 48This module (depending on the loaded configuration) identifies the user 49based on the HTTP request. 50 51@see pep.pl for _authorization_ issues. 52*/ 53 54%! authenticate(+Request, -Identity:dict) is det. 55% 56% Establish the identity behind the HTTP Request. There are two 57% scenarios. 58% 59% - The entire server is protected using HTTP authentication. In 60% this case this predicate may throw an HTTP challenge or a 61% forbidden exception. 62% - The server allows for mixed anonymous and logged in usage. Login 63% may use HTTP or federated login (oauth2). 64% 65% @throws http_reply(_) HTTP authentication and permission exceptions 66% if config-available/auth_http_always.pl is enabled. 67 68authenticate(Request, Auth) :- 69 http_peer(Request, Peer), 70 http_auth(Request, HTTPAuth), 71 profile_auth(Request, ProfileAuth), 72 Auth2 = HTTPAuth.put(ProfileAuth).put(peer, Peer), 73 identity(Request, Auth2, Auth), 74 debug(authenticate, 'Identity: ~p', [Auth]). 75 76:- multifile 77 swish_config:user_info/3, 78 swish_config:authenticate/2, 79 swish_config:user_profile/2. 80 81http_auth(Request, Auth) :- 82 ( swish_config:authenticate(Request, User) % throws http_reply(_) 83 -> true 84 ; swish_config:user_info(Request, local, UserInfo), 85 User = UserInfo.get(user) 86 ), 87 !, 88 Auth = auth{user:User, identity_provider:local, external_identity:User}. 89http_auth(_Request, auth{}). 90 91profile_auth(Request, Auth) :- 92 swish_config:user_profile(Request, Profile), 93 Pattern = _{ identity_provider: _, 94 external_identity: _, 95 profile_id:_}, 96 Pattern :< Profile, 97 Auth = auth{}.put(Pattern). 98profile_auth(_, auth{}). 99 100identity(Request, Auth0, Auth) :- 101 _{identity_provider:Provider, external_identity:ExtID} :< Auth0, 102 !, 103 ( swish_config:user_info(Request, Provider, UserInfo), 104 is_dict(UserInfo, Tag), 105 ( var(Tag) 106 -> Tag = user_info 107 ; true 108 ) 109 -> true 110 ; UserInfo = user_info{} 111 ), 112 atomic_list_concat([Provider,ExtID], :, Identity), 113 Auth = Auth0.put(_{identity:Identity, user_info:UserInfo}). 114identity(_, Auth, Auth). 115 116 117%! user_property(+Identity, ?Property) is nondet. 118% 119% True when Identity has Property. Defined properties are: 120% 121% - peer(Atom) 122% Remote IP address 123% - identity(Atom) 124% Identity as provided by some identity provider 125% - identity_provider(Atom) 126% Subsystem that identified the user 127% - external_identity(Atom) 128% Identity as provided by the identity_provider 129% - profile_id(Atom) 130% Identifier of the profile we have on this user. 131% - login(Atom) 132% Same as identity_provider(Atom) 133% - name(Atom) 134% Name associated with the identity 135% - email(Atom) 136% Email associated with the identity 137 138user_property(Identity, Property) :- 139 current_user_property(Property, How), 140 user_property_impl(Property, How, Identity). 141 142user_property_impl(Property, dict, Identity) :- !, 143 Property =.. [Name,Value], 144 Value = Identity.get(Name). 145user_property_impl(Property, broadcast, Identity) :- 146 broadcast_request(identity_property(Identity, Property)). 147user_property_impl(login(By), _, Identity) :- 148 By = Identity.get(identity_provider). 149 150 151current_user_property(peer(_Atom), dict). 152current_user_property(identity(_Atom), dict). 153current_user_property(external_identity(_String), dict). 154current_user_property(identity_provider(_Atom), dict). 155current_user_property(profile_id(_Atom), dict). 156current_user_property(avatar(_String), dict). 157 158current_user_property(login(_IdProvider), derived). 159current_user_property(name(_Name), broadcast). 160current_user_property(email(_Email), broadcast). 161 162 163 /******************************* 164 * PENGINE HOOKS * 165 *******************************/ 166 167%! pengines:authentication_hook(+Request, +Application, -User) 168% 169% Is called from the /pengine/create request to establish the logged 170% in user. 171 172:- multifile pengines:authentication_hook/3. 173 174penginesauthentication_hook(Request, _Application, User) :- 175 authenticate(Request, User)