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(config_auth_stackoverflow, []). 37:- use_module(swish(lib/oauth2)). 38:- use_module(swish(lib/plugin/login)). 39:- use_module(library(http/http_open)). 40:- use_module(library(http/http_dispatch)). 41:- use_module(library(http/http_header)). 42:- use_module(library(http/http_session)). 43:- use_module(library(http/http_json)). 44:- use_module(library(http/json)). 45:- use_module(library(http/http_path)). 46:- use_module(library(uri)). 47:- use_module(library(debug)). 48:- use_module(library(apply)).
69:- multifile 70 oauth2:login/3, 71 oauth2:server_attribute/3, 72 swish_config:login_item/2, % -Server, -HTML_DOM 73 swish_config:login/2, % +Server, +Request 74 swish_config:user_info/2. % +Request, ?Server, -Info 75 76:- http_set_session_options([create(noauto)]). 77 78:- http_handler(swish(logout), stackexchange_logout, []).
EDIT:
95oauth2server_attribute(stackexchange, url, 96 'https://stackexchange.com'). 97oauth2server_attribute(stackexchange, redirect_uri, 98 'http://cplint.eu/oauth2/stackexchange/reply'). 99oauth2server_attribute(stackexchange, authorization_endpoint, 100 '/oauth'). 101oauth2server_attribute(stackexchange, token_endpoint, 102 '/oauth/access_token'). 103oauth2server_attribute(stackexchange, api_endpoint, 104 'https://api.stackexchange.com'). 105oauth2server_attribute(stackexchange, client_id, 106 '12983'). 107oauth2server_attribute(stackexchange, client_secret, 108 'gTRdP5ub6ktEVrBXmOaOCA(('). 109oauth2server_attribute(stackexchange, key, 110 'eAFMxVCc)KtvEvIZvCfiOA(('). 111oauth2server_attribute(stackexchange, site, 112 'stackoverflow'). 113oauth2server_attribute(stackexchange, scope, 114 ''). 115 116 117 /******************************* 118 * SWISH HOOKS * 119 *******************************/ 120 121swish_configlogin_item(stackexchange, 10-Item) :- 122 http_absolute_location(icons('so-icon.png'), Img, []), 123 Item = img([ src(Img), 124 class('login-with'), 125 'data-server'(stackexchange), 126 'data-frame'(popup), 127 title('Login with StackOverflow') 128 ]). 129 130swish_configlogin(stackexchange, Request) :- 131 oauth2_login(Request, [server(stackexchange)]). 132 133oauth2login(_Request, stackexchange, TokenInfo) :- 134 debug(oauth, 'TokenInfo: ~p', [TokenInfo]), 135 stackexchange_me(TokenInfo.access_token, Claim), 136 debug(oauth, 'Claim: ~p', [Claim]), 137 map_user_info(Claim, UserInfo), 138 http_open_session(_SessionID, []), 139 session_remove_user_data, 140 http_session_assert(oauth2(stackexchange, TokenInfo)), 141 http_session_assert(user_info(stackexchange, UserInfo)), 142 reply_logged_in([ identity_provider('StackOverflow'), 143 name(UserInfo.name), 144 user_info(UserInfo) 145 ]).
user_info
endpoint.
Instead, we must use the =/me= API. This is a little unlucky as we
need to duplicate some infrastructure from the generic oauth2.pl
module.154stackexchange_me(AccessToken, Info) :- 155 oauth2:server_attribute(stackexchange, api_endpoint, URLBase), 156 oauth2:server_attribute(stackexchange, client_id, ClientID), 157 oauth2:server_attribute(stackexchange, client_secret, ClientSecret), 158 oauth2:server_attribute(stackexchange, key, Key), 159 oauth2:server_attribute(stackexchange, site, Site), 160 161 uri_extend(URLBase, '/2.2/me', 162 [ key(Key), 163 site(Site), 164 access_token(AccessToken) 165 ], 166 URL), 167 168 setup_call_cleanup( 169 http_open(URL, In, 170 [ authorization(basic(ClientID, ClientSecret)), 171 header(content_type, ContentType), 172 status_code(Code) 173 ]), 174 read_reply(Code, ContentType, In, Info0), 175 close(In)), 176 me_info(Info0, Info). 177 178me_info(Info, Me) :- 179 [Me] = Info.get(items), 180 !. 181me_info(Info, Info). 182 183 184read_reply(Code, ContentType, In, Dict) :- 185 debug(oauth, '/me returned ~p ~p', [Code, ContentType]), 186 http_parse_header_value(content_type, ContentType, Parsed), 187 read_reply2(Code, Parsed, In, Dict).
195read_reply2(200, media(application/json, _Attributes), In, Dict) :- !, 196 json_read_dict(In, Dict). 197read_reply2(Code, media(application/json, _Attributes), In, 198 error{code:Code, details:Details}) :- !, 199 json_read_dict(In, Details). 200read_reply2(Code, Type, In, 201 error{code:Code, message:Reply}) :- 202 debug(oauth(token), 'Got code ~w, type ~q', [Code, Type]), 203 read_string(In, _, Reply).
210stackexchange_logout(_Request) :-
211 catch(session_remove_user_data, _, true),
212 reply_logged_out([]).
218swish_configuser_info(_Request, stackexchange, UserInfo) :-
219 http_in_session(_SessionID),
220 http_session_data(user_info(stackexchange, UserInfo)).
226map_user_info(Dict0, Dict) :- 227 dict_pairs(Dict0, Tag, Pairs0), 228 maplist(map_user_field, Pairs0, Pairs), 229 http_link_to_id(stackexchange_logout, [], LogoutURL), 230 dict_pairs(Dict, Tag, 231 [ identity_provider-stackexchange, 232 auth_method-oauth2, 233 logout_url-LogoutURL 234 | Pairs 235 ]). 236 237map_user_field(display_name-Name, name-Name) :- !. 238map_user_field(profile_image-URL, picture-URL) :- !. 239map_user_field(link-URL, profile_url-URL) :- !. 240map_user_field(user_id-Id, external_identity-SId) :- !, 241 format(string(SId), '~w', [Id]). 242map_user_field(Field, Field). 243 244session_remove_user_data :- 245 http_session_retractall(oauth2(_,_)), 246 http_session_retractall(user_info(_,_)). 247 248 249 /******************************* 250 * URI BASICS * 251 *******************************/
257uri_extend(Base, Relative, Query, URI) :-
258 uri_resolve(Relative, Base, URI0),
259 uri_extend_query(URI0, Query, URI).
266uri_extend_query(URI0, Query, URI) :- 267 uri_components(URI0, Components0), 268 extend_query(Components0, Query, Query1), 269 uri_data(search, Components0, Query1, Components1), 270 uri_components(URI, Components1). 271 272extend_query(Components, QueryEx, Query) :- 273 uri_data(search, Components, Query0), 274 ( var(Query0) 275 -> uri_query_components(Query, QueryEx) 276 ; uri_query_components(Query0, Q0), 277 merge_components(Q0, QueryEx, Q), 278 uri_query_components(Query, Q) 279 ). 280 281merge_components([], Q, Q). 282merge_components([N=_|T0], Q1, Q) :- 283 memberchk(N=_, Q1), !, 284 merge_components(T0, Q1, Q). 285merge_components([H|T0], Q1, [H|Q]) :- 286 merge_components(T0, Q1, Q)
Enable login with stackexchange
This module allows for configures _login with Stack exchange. To enable this module:
http://localhost:3050/oauth2/stackexchange/reply
config-enabled
*/