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:
96oauth2server_attribute(stackexchange, url, 97 'https://stackexchange.com'). 98oauth2server_attribute(stackexchange, redirect_uri, 99 'http://localhost:3050/oauth2/stackexchange/reply'). 100oauth2server_attribute(stackexchange, authorization_endpoint, 101 '/oauth'). 102oauth2server_attribute(stackexchange, token_endpoint, 103 '/oauth/access_token'). 104oauth2server_attribute(stackexchange, api_endpoint, 105 'https://api.stackexchange.com'). 106oauth2server_attribute(stackexchange, client_id, 107 '****'). 108oauth2server_attribute(stackexchange, client_secret, 109 '****'). 110oauth2server_attribute(stackexchange, key, 111 '****'). 112oauth2server_attribute(stackexchange, site, 113 'stackoverflow'). 114oauth2server_attribute(stackexchange, scope, 115 ''). 116 117 118 /******************************* 119 * SWISH HOOKS * 120 *******************************/ 121 122swish_configlogin_item(stackexchange, 10-Item) :- 123 http_absolute_location(icons('so-icon.png'), Img, []), 124 Item = img([ src(Img), 125 class('login-with'), 126 'data-server'(stackexchange), 127 'data-frame'(popup), 128 title('Login with StackOverflow') 129 ]). 130 131swish_configlogin(stackexchange, Request) :- 132 oauth2_login(Request, [server(stackexchange)]). 133 134oauth2login(_Request, stackexchange, TokenInfo) :- 135 debug(oauth, 'TokenInfo: ~p', [TokenInfo]), 136 stackexchange_me(TokenInfo.access_token, Claim), 137 debug(oauth, 'Claim: ~p', [Claim]), 138 map_user_info(Claim, UserInfo), 139 http_open_session(_SessionID, []), 140 session_remove_user_data, 141 http_session_assert(oauth2(stackexchange, TokenInfo)), 142 http_session_assert(user_info(stackexchange, UserInfo)), 143 reply_logged_in([ identity_provider('StackOverflow'), 144 name(UserInfo.name), 145 user_info(UserInfo) 146 ]).
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.155stackexchange_me(AccessToken, Info) :- 156 oauth2:server_attribute(stackexchange, api_endpoint, URLBase), 157 oauth2:server_attribute(stackexchange, client_id, ClientID), 158 oauth2:server_attribute(stackexchange, client_secret, ClientSecret), 159 oauth2:server_attribute(stackexchange, key, Key), 160 oauth2:server_attribute(stackexchange, site, Site), 161 162 uri_extend(URLBase, '/2.2/me', 163 [ key(Key), 164 site(Site), 165 access_token(AccessToken) 166 ], 167 URL), 168 169 setup_call_cleanup( 170 http_open(URL, In, 171 [ authorization(basic(ClientID, ClientSecret)), 172 header(content_type, ContentType), 173 status_code(Code) 174 ]), 175 read_reply(Code, ContentType, In, Info0), 176 close(In)), 177 me_info(Info0, Info). 178 179me_info(Info, Me) :- 180 [Me] = Info.get(items), 181 !. 182me_info(Info, Info). 183 184 185read_reply(Code, ContentType, In, Dict) :- 186 debug(oauth, '/me returned ~p ~p', [Code, ContentType]), 187 http_parse_header_value(content_type, ContentType, Parsed), 188 read_reply2(Code, Parsed, In, Dict).
196read_reply2(200, media(application/json, _Attributes), In, Dict) :- !, 197 json_read_dict(In, Dict, [default_tag(#)]). 198read_reply2(Code, media(application/json, _Attributes), In, 199 error{code:Code, details:Details}) :- !, 200 json_read_dict(In, Details, [default_tag(#)]). 201read_reply2(Code, Type, In, 202 error{code:Code, message:Reply}) :- 203 debug(oauth(token), 'Got code ~w, type ~q', [Code, Type]), 204 read_string(In, _, Reply).
211stackexchange_logout(_Request) :-
212 catch(session_remove_user_data, _, true),
213 reply_logged_out([]).
219swish_configuser_info(_Request, stackexchange, UserInfo) :-
220 http_in_session(_SessionID),
221 http_session_data(user_info(stackexchange, UserInfo)).
227map_user_info(Dict0, Dict) :- 228 dict_pairs(Dict0, Tag, Pairs0), 229 maplist(map_user_field, Pairs0, Pairs), 230 http_link_to_id(stackexchange_logout, [], LogoutURL), 231 dict_pairs(Dict, Tag, 232 [ identity_provider-stackexchange, 233 auth_method-oauth2, 234 logout_url-LogoutURL 235 | Pairs 236 ]). 237 238map_user_field(display_name-Name, name-Name) :- !. 239map_user_field(profile_image-URL, picture-URL) :- !. 240map_user_field(link-URL, profile_url-URL) :- !. 241map_user_field(user_id-Id, external_identity-SId) :- !, 242 format(string(SId), '~w', [Id]). 243map_user_field(Field, Field). 244 245session_remove_user_data :- 246 http_session_retractall(oauth2(_,_)), 247 http_session_retractall(user_info(_,_)). 248 249 250 /******************************* 251 * URI BASICS * 252 *******************************/
258uri_extend(Base, Relative, Query, URI) :-
259 uri_resolve(Relative, Base, URI0),
260 uri_extend_query(URI0, Query, URI).
267uri_extend_query(URI0, Query, URI) :- 268 uri_components(URI0, Components0), 269 extend_query(Components0, Query, Query1), 270 uri_data(search, Components0, Query1, Components1), 271 uri_components(URI, Components1). 272 273extend_query(Components, QueryEx, Query) :- 274 uri_data(search, Components, Query0), 275 ( var(Query0) 276 -> uri_query_components(Query, QueryEx) 277 ; uri_query_components(Query0, Q0), 278 merge_components(Q0, QueryEx, Q), 279 uri_query_components(Query, Q) 280 ). 281 282merge_components([], Q, Q). 283merge_components([N=_|T0], Q1, Q) :- 284 memberchk(N=_, Q1), !, 285 merge_components(T0, Q1, Q). 286merge_components([H|T0], Q1, [H|Q]) :- 287 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
*/