29
30:- module(plweb_openid,
31 [ site_user/2, 32 site_user_logged_in/1, 33 site_user_property/2, 34 grant/2, 35 revoke/2, 36 authenticate/3, 37 user_profile_link//1, 38 current_user//1, 39 current_user//0,
40 login_link//1, 41 redirect_master/1 42 ]). 43:- use_module(library(http/http_dispatch)). 44:- use_module(library(http/http_parameters)). 45:- use_module(library(http/http_session)). 46:- use_module(library(http/http_wrapper)). 47:- use_module(library(http/http_openid)). 48:- use_module(library(http/http_header)). 49:- use_module(library(http/http_path)). 50:- use_module(library(http/html_write)). 51:- use_module(library(http/js_write)). 52:- use_module(library(http/http_json)). 53:- use_module(library(http/html_head)). 54:- use_module(library(http/http_authenticate)). 55:- use_module(library(http/http_host)). 56:- use_module(library(http/recaptcha)). 57:- use_module(library(http/http_stream)). 58:- use_module(library(persistency)). 59:- use_module(library(settings)). 60:- use_module(library(debug)). 61:- use_module(library(uuid)). 62:- use_module(library(option)). 63:- use_module(library(error)). 64:- use_module(library(lists)). 65:- use_module(library(pairs)). 66:- use_module(library(google_client)). 67
68:- use_module(parms). 69:- use_module(review). 70:- use_module(pack). 71:- use_module(wiki). 72:- use_module(markitup). 73:- use_module(tagit). 74:- use_module(post). 75
90
91:- multifile
92 http_openid:openid_hook/1. 93
94:- persistent
95 openid_user_server(user:atom,
96 server:atom),
97 site_user(uuid:atom,
98 openid:atom,
99 name:atom,
100 email:atom,
101 home_url:atom),
102 user_description(uuid:atom,
103 description:atom),
104 stay_signed_in(openid:atom,
105 cookie:atom,
106 peer:atom,
107 time:integer,
108 expires:integer),
109 granted(uuid:atom,
110 token:atom). 111
112:- initialization
113 db_attach('openid.db',
114 [ sync(close)
115 ]). 116
117:- http_handler(root(user/create_profile), create_profile, []). 118:- http_handler(root(user/submit_profile), submit_profile, []). 119:- http_handler(root(user/logout), logout, []). 120:- http_handler(root(user/view_profile), view_profile, []). 121:- http_handler(root(user/verify), verify_user, []). 122:- http_handler(root(user/list), list_users, []). 123:- http_handler(root(user/grant), grant_user, []). 124
125
126 129
130site_user_property(UUID, uuid(UUID)) :-
131 ( site_user(UUID, _, _, _, _)
132 -> true
133 ).
134site_user_property(UUID, openid(OpenId)) :-
135 site_user(UUID, OpenId, _, _, _).
136site_user_property(UUID, name(Name)) :-
137 site_user(UUID, _, Name, _, _).
138site_user_property(UUID, email(Email)) :-
139 site_user(UUID, _, _, Email, _).
140site_user_property(UUID, home_url(Home)) :-
141 site_user(UUID, _, _, _, Home).
142site_user_property(UUID, granted(Token)) :-
143 granted(UUID, Token).
144site_user_property(UUID, granted_list(Tokens)) :-
145 ( site_user(UUID, _, _, _, _)
146 -> findall(Token, granted(UUID, Token), Tokens)
147 ).
148
149set_user_property(UUID, Prop) :-
150 site_user_property(UUID, Prop), !.
151set_user_property(UUID, openid(OpenId)) :-
152 retract_site_user(UUID, _OldID, Name, Email, Home),
153 assert_site_user(UUID, OpenId, Name, Email, Home).
154
155
156 159
164
165grant(User, Token) :-
166 ground_user(User),
167 must_be(atom, Token),
168 granted(User, Token), !.
169grant(User, Token) :-
170 assert_granted(User, Token).
171
172revoke(User, Token) :-
173 ground_user(User),
174 must_be(atom, Token),
175 \+ granted(User, Token), !.
176revoke(User, Token) :-
177 retract_granted(User, Token).
178
179ground_user(User) :-
180 must_be(atom, User),
181 site_user(User, _, _, _, _), !.
182ground_user(User) :-
183 existence_error(user, User).
184
185
189
190grant_user(Request) :-
191 catch(( http_read_json_dict(Request, Data),
192 debug(grant, '~q', [Data]),
193 admin_granted(Request),
194 atom_string(UUID, Data.uuid),
195 atom_string(Token, Data.token),
196 ( Data.value == true
197 -> grant(UUID, Token)
198 ; revoke(UUID, Token)
199 )
200 ), E,
201 throw(http_reply(bad_request(E)))),
202 throw(http_reply(no_content)).
203
204admin_granted(_Request) :-
205 site_user_logged_in(User),
206 site_user_property(User, granted(admin)), !.
207admin_granted(Request) :-
208 memberchk(path(Path), Request),
209 throw(http_reply(forbidden(Path))).
210
215
216authenticate(Request, Token, [UUID,Name]) :-
217 site_user_logged_in(UUID),
218 ( site_user_property(UUID, granted(Token))
219 -> site_user_property(UUID, name(Name))
220 ; option(path(Path), Request),
221 permission_error(access, http_location, Path)
222 ).
223authenticate(Request, Token, Fields) :-
224 redirect_master(Request),
225 ( http_authenticate(basic(passwd), Request, Fields)
226 -> true
227 ; format(atom(Msg), 'SWI-Prolog ~w authoring', [Token]),
228 throw(http_reply(authorise(basic, Msg)))
229 ).
230
231
232 235
236:- multifile recaptcha:key/2. 237
238:- setting(recaptcha:public_key, atom, '',
239 'reCAPTCHA public key'). 240:- setting(recaptcha:private_key, atom, '',
241 'reCAPTCHA private key'). 242
243recaptcha:key(public, Key) :- setting(recaptcha:public_key, Key).
244recaptcha:key(private, Key) :- setting(recaptcha:private_key, Key).
245
250
251site_user(Request, User) :-
252 openid_user(Request, OpenID, []),
253 ensure_profile(OpenID, User).
254
255ensure_profile(OpenID, User) :-
256 ( site_user_property(User, openid(OpenID))
257 -> true
258 ; http_current_request(Request),
259 option(request_uri(RequestURI), Request),
260 http_link_to_id(create_profile, [return(RequestURI)], HREF),
261 http_redirect(moved_temporary, HREF, Request)
262 ).
263
267
268site_user_logged_in(User) :-
269 openid_logged_in(OpenID),
270 site_user_property(User, openid(OpenID)).
271
272
277
278create_profile(Request) :-
279 openid_user(Request, OpenID, []),
280 http_parameters(Request,
281 [ return(Return, [])
282 ]),
283 reply_html_page(
284 user(create_profile),
285 title('Create user profile for SWI-Prolog'),
286 \create_profile(OpenID, Return)).
287
288
289create_profile(OpenID, Return) -->
290 { ( site_user_property(User, openid(OpenID))
291 -> Op = 'Update profile'
292 ; uuid(User), 293 Op = 'Create profile'
294 )
295 },
296 html(h1(class(wiki), Op)),
297 { http_link_to_id(submit_profile, [], Action),
298 user_init_property(User, name(Name), ''),
299 user_init_property(User, email(Email), ''),
300 user_init_property(User, home_url(HomeURL), '')
301 },
302 html(form([ class(create_profile), method('POST'), action(Action) ],
303 [ input([type(hidden), name(return), value(Return)]),
304 input([type(hidden), name(uuid), value(User)]),
305 table([ tr([th('OpenID'), td(input([ name(openid),
306 value(OpenID),
307 disabled(disabled)
308 ]))]),
309 tr([th('Name'), td(input([ name(name),
310 value(Name),
311 placeholder('Displayed name')
312 ]))]),
313 tr([th('Email'), td(input([ name(email),
314 value(Email),
315 placeholder('Your E-mail address')
316 ]))]),
317 tr([th('Home URL'), td(input([ name(home_url),
318 value(HomeURL),
319 placeholder('http://')
320 ]))]),
321 \description(User),
322 tr(td(colspan(2), \recaptcha([]))),
323 tr(td([colspan(2), align(right)],
324 input([type(submit), value(Op)])))
325 ])
326 ])),
327 expain_create_profile.
328
329user_init_property(User, P, Default) :-
330 ( site_user_property(User, P)
331 -> true
332 ; http_session_data(ax(AX)),
333 ax(P, AX)
334 -> true
335 ; arg(1, P, Default)
336 ).
337
338ax(email(AX.get(email)), AX).
339ax(name(AX.get(name)), AX) :- !.
340ax(name(Name), AX) :-
341 atomic_list_concat([AX.get(firstname), AX.get(lastname)], ' ', Name), !.
342ax(name(AX.get(nickname)), AX).
343
344expain_create_profile -->
345 html({|html||
346 <div class="smallprint">
347 On this page, we ask you to proof you are human and
348 create a minimal profile. Your name is displayed along with comments
349 that you create. Your E-mail and home URL are used to detect authorship of
350 packs. Your E-mail and home URL will not be displayed,
351 nor be used for spamming and not be handed to third parties.
352 The editor can be used to add a short description about yourself.
353 This description is shown on your profile page that collects
354 your packages and ratings and reviews you performed.
355 </div>
356 |}).
357
361
362description(UUID) -->
363 { ( user_description(UUID, Description)
364 -> Extra = [value(Description)]
365 ; Extra = []
366 )
367 },
368 html(tr(td(colspan(2),
369 \markitup([ id(description),
370 markup(pldoc),
371 cold(60),
372 rows(10)
373 | Extra
374 ])))).
375
379
380submit_profile(Request) :-
381 openid_user(Request, OpenID, []),
382 recaptcha_parameters(ReCAPTCHA),
383 http_parameters(Request,
384 [ uuid(User, []),
385 name(Name0, [optional(true), default(anonymous)]),
386 email(Email0, [optional(true), default('')]),
387 home_url(Home0, [optional(true), default('')]),
388 description(Descr, [optional(true), default('')]),
389 return(Return, [])
390 | ReCAPTCHA
391 ]),
392 ( catch(recaptcha_verify(Request, ReCAPTCHA), E, true)
393 -> ( var(E)
394 -> retractall_site_user(User, OpenID, _, _, _),
395 normalize_space(atom(Name), Name0),
396 normalize_space(atom(Email), Email0),
397 normalize_space(atom(Home), Home0),
398 assert_site_user(User, OpenID, Name, Email, Home),
399 update_description(User, Descr),
400 http_redirect(moved_temporary, Return, Request)
401 ; E = error(domain_error(recaptcha_response, _), _)
402 -> retry_captcha('CAPTCHA required', '')
403 ; message_to_string(E, Msg)
404 -> retry_captcha('CAPTCHA processing error', Msg)
405 )
406 ; retry_captcha('CAPTCHA verification failed', '')
407 ).
408
409retry_captcha(Why, Warning) :-
410 reply_html_page(
411 plain,
412 title('CAPTCHA failed'),
413 [ h1(class(wiki), Why),
414 p(class(error), Warning),
415 p([ 'Please use the back button of your browser and ',
416 'try again'
417 ])
418 ]).
419
420
421update_description(UUID, '') :- !,
422 retractall_user_description(UUID, _).
423update_description(UUID, Description) :- !,
424 retractall_user_description(UUID, _),
425 assert_user_description(UUID, Description).
426
435
436view_profile(Request) :-
437 http_parameters(Request,
438 [ user(UUID, [ optional(true) ])
439 ]),
440 ( site_user_logged_in(User)
441 -> ( User = UUID
442 -> ( site_user_property(User, granted(admin))
443 -> Options = [view(admin), edit_link(true)]
444 ; Options = [view(private), edit_link(true)]
445 )
446 ; site_user_property(User, granted(admin))
447 -> Options = [view(admin)]
448 ; Options = [view(public)]
449 )
450 ; ( var(UUID)
451 -> existence_error(http_parameter, user)
452 ; Options = [view(public)]
453 )
454 ),
455 site_user_property(UUID, name(Name)),
456 reply_html_page(
457 user(view_profile(UUID)),
458 title('User ~w'-[Name]),
459 [ \edit_link(UUID, Options),
460 \view_profile(UUID, Options)
461 ]).
462
463view_profile(UUID, Options) -->
464 private_profile(UUID, Options),
465 user_description(UUID, Options),
466 user_tags(UUID, []),
467 user_posts(UUID, annotation),
468 user_posts(UUID, news),
469 user_packs(UUID),
470 profile_reviews(UUID).
471
477
478private_profile(UUID, Options) -->
479 { option(view(private), Options)
480 ; option(view(admin), Options)
481 }, !,
482 html([ div(class('private-profile'),
483 [ h2(class(wiki),
484 [ 'Private profile data',
485 \link_list_users
486 ]),
487 table([ \profile_data(UUID, 'Name', name),
488 \profile_data(UUID, 'OpenID', openid),
489 \profile_data(UUID, 'E-Mail', email),
490 \profile_data(UUID, 'Home page', home_url)
491 | \admin_profile(UUID, Options)
492 ])
493 ]),
494 div(class(smallprint),
495 'The above private information is shown only to the owner.')
496 ]).
497private_profile(_, _) --> [].
498
499admin_profile(UUID, Options) -->
500 { option(view(admin), Options) }, !,
501 html([ \profile_data(UUID, 'UUID', uuid),
502 \profile_data(UUID, 'Granted', granted_list)
503 ]).
504admin_profile(_, _) --> [].
505
506link_list_users -->
507 { http_link_to_id(list_users, [], HREF)
508 },
509 html(a([ class('list-other-users'),
510 style('float:right;'),
511 href(HREF)
512 ], 'other users')).
513
514create_profile_link(HREF) :-
515 http_current_request(Request),
516 option(request_uri(Here), Request),
517 http_link_to_id(create_profile, [return(Here)], HREF).
518
519profile_data(UUID, Label, Field) -->
520 { Term =.. [Field,Value],
521 site_user_property(UUID, Term),
522 ( value_dom(Field, UUID, Value, DOM)
523 -> true
524 )
525 },
526 html(tr([ th([Label,:]),
527 td(DOM)
528 ])).
529
530value_dom(name, _, Name, Name).
531value_dom(uuid, _, UUID, UUID).
532value_dom(email, _, Email, a(href('mailto:'+Email), Email)).
533value_dom(granted_list, UUID, Tokens, \token_list(UUID, Tokens, [edit(true)])).
534value_dom(_, _, URL, a(href(URL), URL)).
535
539
540user_description(UUID, _Options) -->
541 { user_description(UUID, Description),
542 Description \== '', !,
543 atom_codes(Description, Codes),
544 wiki_file_codes_to_dom(Codes, /, DOM0),
545 clean_dom(DOM0, DOM)
546 },
547 html(DOM).
548user_description(_UUID, Options) -->
549 { option(edit_link(true), Options),
550 create_profile_link(Edit)
551 },
552 html([ i('No description.'),
553 ' Click ', a(href(Edit), here), ' to create one'
554 ]).
555user_description(_, _) --> [].
556
557clean_dom([p(X)], X) :- !.
558clean_dom(X, X).
559
560edit_link(_UUID, Options) -->
561 { option(edit_link(true), Options), !,
562 create_profile_link(Edit)
563 },
564 html(div(class('edit-profile'),
565 [ a(href(Edit), 'Edit'), ' profile'])).
566edit_link(_, _) --> [].
567
568
573
574user_packs(UUID) -->
575 { setof(Pack, current_pack([author(UUID)], Pack), Packs), !,
576 sort_packs(rating, Packs, Sorted),
577 site_user_property(UUID, name(Name))
578 },
579 html([ h2(class(wiki), 'Packages by ~w'-[Name])
580 ]),
581 pack_table(Sorted, []),
582 html([ div(class(smallprint),
583 [ 'This list contains packages whose author name, e-mail ',
584 'or homepage url matches the profile information.'
585 ])
586 ]).
587user_packs(_) -->
588 [].
589
590
594
595list_users(_Request) :-
596 site_user_logged_in(User), !,
597 ( site_user_property(User, granted(admin))
598 -> ShowAdmin = true
599 ; ShowAdmin = false
600 ),
601 findall(Kudos-Details,
602 site_kudos(_UUID, Details, Kudos),
603 Pairs),
604 keysort(Pairs, Sorted),
605 pairs_values(Sorted, Users),
606 reverse(Users, BestFirst),
607 reply_html_page(
608 user(list),
609 title('SWI-Prolog site users'),
610 [ \explain_user_listing,
611 \html_requires(css('stats.css')),
612 table(class(block),
613 [ \user_table_header(ShowAdmin)
614 | \user_rows(BestFirst, ShowAdmin)
615 ])
616 ]).
617list_users(_Request) :-
618 reply_html_page(
619 user(list),
620 title('Permission denied'),
621 [ \explain_user_listing_not_logged_on
622 ]).
623
624site_kudos(UUID, Details, Kudos) :-
625 Details = _{ user:UUID,
626 news:NewsArticles,
627 annotations:Annotations,
628 reviews:Reviews,
629 tags:Tags,
630 votes:Up-Down
631 },
632 site_user(UUID, _, _, _, _),
633 user_post_count(UUID, news, NewsArticles),
634 user_post_count(UUID, annotation, Annotations),
635 user_review_count(UUID, Reviews),
636 user_tag_count(UUID, Tags),
637 user_vote_count(UUID, Up, Down),
638 Kudos is ( NewsArticles*20 +
639 Reviews*10 +
640 Annotations*10 +
641 Tags*2 +
642 Up+Down
643 ).
644
645explain_user_listing -->
646 html({|html||
647 <p>Below is a listing of all registered users with some
648 basic properties. This is list only visible to other
649 registered users.
650 |}).
651
652explain_user_listing_not_logged_on -->
653 html({|html||
654 <h1 class="wiki">Permission denied</h1>
655
656 <p class="warning">A listing of all registered users is only
657 available to users who are logged in.
658 |}).
659
660user_rows([], _) --> [].
661user_rows([H|T], ShowAdmin) --> user_row(H, ShowAdmin), user_rows(T, ShowAdmin).
662
(ShowAdmin) -->
664 html(tr([th('User'),
665 th('#Comments'),
666 th('#Reviews'),
667 th('#Votes'),
668 th('#Tags'),
669 \admin_header(ShowAdmin)
670 ])).
671
(true) --> !,
673 html([ th('Granted'),
674 th('E-mail')
675 ]).
676admin_header(_) --> [].
677
678user_row(Details, ShowAdmin) -->
679 { Up-Down = Details.votes },
680 html(tr([td(\user_profile_link(Details.user)),
681 td(Details.annotations),
682 td(Details.reviews),
683 td('+~d-~d'-[Up,Down]),
684 td(Details.tags),
685 \admin_columns(Details.user, ShowAdmin)
686 ])).
687
688admin_columns(UUID, true) --> !,
689 { site_user_property(UUID, granted_list(Tokens)),
690 site_user_property(UUID, email(Email))
691 },
692 html([ td(\token_list(UUID, Tokens, [])),
693 td(\email(Email))
694 ]).
695admin_columns(_, _) --> [].
696
697token_list(UUID, Tokens, Options) -->
698 { option(edit(true), Options), !,
699 http_link_to_id(grant_user, [], Action)
700 },
701 html([ \token(wiki, UUID, Tokens),
702 \token(news, UUID, Tokens),
703 \token(admin, UUID, Tokens)
704 ]),
705 html_post(script, \granted_script(Action)).
706token_list(_, Tokens, _Options) -->
707 token_list(Tokens).
708
709token_list([]) --> [].
710token_list([H|T]) -->
711 html(H),
712 ( {T==[]}
713 -> []
714 ; html([', ']),
715 token_list(T)
716 ).
717
718token(Token, UUID, Active) -->
719 { memberchk(Token, Active)
720 -> Extra = [checked(checked)]
721 ; Extra = []
722 },
723 html([ input([ type(checkbox),
724 class(grant),
725 name(Token),
726 value(UUID)
727 | Extra
728 ]),
729 Token
730 ]).
731
732granted_script(Action) -->
733 js_script({|javascript(Action)||
734$(document).ready(function() {
735 $("input.grant").click(function(e)
736 { e.preventDefault();
737 var checkbox = $(this);
738 var checked = checkbox.prop("checked");
739 var token = checkbox.prop("name");
740 var UUID = checkbox.prop("value");
741 $.ajax(Action,
742 { "contentType": "application/json; charset=utf-8",
743 "dataType": "json",
744 "data": JSON.stringify({ uuid: UUID,
745 value: checked,
746 token: token
747 }),
748 "success": function() {
749 checkbox.prop("checked", checked);
750 },
751 "type": "POST"
752 });
753 });
754});
755 |}).
756
757
758email(Mail) -->
759 html(a(href('mailto:'+Mail), Mail)).
760
761
762 765
769
770user_profile_link(UUID) -->
771 { site_user_property(UUID, name(Name)),
772 http_link_to_id(view_profile, [user(UUID)], HREF)
773 }, !,
774 html(a([class(user), href(HREF)], Name)).
775
776
777 780
781stay_login_cookie(swipl_login).
782
783http_openid:openid_hook(trusted(OpenId, Server)) :-
784 openid_user_server(OpenId, Server), !.
785http_openid:openid_hook(trusted(OpenId, Server)) :-
786 assert_openid_user_server(OpenId, Server), !.
787http_openid:openid_hook(stay_signed_in(OpenId)) :-
788 assertion(in_header_state),
789 http_session_cookie(Cookie),
790 get_time(NowF),
791 Now is round(NowF),
792 http_current_request(Request),
793 http_peer(Request, Peer),
794 Expires is Now+31*24*60*60, 795 assert_stay_signed_in(OpenId, Cookie, Peer, Now, Expires),
796 http_session_option(path(Path)),
797 debug(openid(stay_signed_in),
798 'Created stay-signed-in for ~q', [OpenId]),
799 http_timestamp(Expires, RFC1123),
800 stay_login_cookie(CookieName),
801 format('Set-Cookie: ~w=~w; Expires=~w; path=~w\r\n',
802 [CookieName, Cookie, RFC1123, Path]).
803http_openid:openid_hook(logout(OpenId)) :-
804 nonvar(OpenId),
805 assertion(in_header_state),
806 retractall_stay_signed_in(OpenId, _, _, _, _),
807 http_session_option(path(Path)),
808 stay_login_cookie(CookieName),
809 format('Set-Cookie: ~w=; \c
810 expires=Tue, 01-Jan-1970 00:00:00 GMT; \c
811 path=~w\r\n',
812 [CookieName, Path]),
813 fail.
814http_openid:openid_hook(logged_in(OpenId)) :-
815 ( debugging(openid_fake(User)),
816 atom(User)
817 -> debug(openid_fake(User), 'Fake login for ~q.', [User]),
818 OpenId = User
819 ; http_in_session(_),
820 http_session_data(openid(OpenId))
821 -> true
822 ; http_current_request(Request),
823 memberchk(cookie(Cookies), Request),
824 memberchk(swipl_login=Cookie, Cookies),
825 stay_signed_in(OpenId, Cookie, _Peer, _Time, _Expires)
826 -> http_open_session(_, []),
827 http_session_assert(openid(OpenId)),
828 debug(openid(stay_signed_in),
829 'Granted stay-signed-in for ~q', [OpenId])
830 ).
832http_openid:openid_hook(x_parameter('https://www.google.com/accounts/o8/ud',
833 openid_shutdown_ack,
834 '2015-04-20')).
835
836
846
847:- multifile
848 yadis:xrds_specified_location/2. 849
850yadis:xrds_specified_location('http://google.com/',
851 'https://www.google.com/accounts/o8/id').
852yadis:xrds_specified_location(StackOverFlow, -) :-
853 sub_atom(StackOverFlow, 0, _, A, 'https://openid.stackexchange.com/'),
854 A > 0.
855
856
:-
858 current_output(CGI),
859 cgi_property(CGI, state(header)), !.
860
861:- http_handler(openid(login), plweb_login_page, [id(swipl_login)]). 862
867
868plweb_login_page(Request) :-
869 redirect_master(Request),
870 memberchk(host(localhost), Request),
871 \+ ( debugging(openid_fake(User)),
872 atom(User)
873 ),
874 \+ http_public_host(Request, localhost, _, []),
875 openid_current_url(Request, URL), !,
876 throw(http_reply(see_other(URL))).
877plweb_login_page(Request) :-
878 http_open_session(_, []),
879 http_parameters(Request,
880 [ 'openid.return_to'(ReturnTo, [])
881 ]),
882 http_link_to_id(verify_user, [], Action),
883 quick_buttons(Buttons),
884 reply_html_page(user(login),
885 [ title('SWI-Prolog login')
886 ],
887 [ \openid_login_form(
888 ReturnTo,
889 [ show_stay(true),
890 action(Action),
891 buttons(Buttons)
892 ]),
893 \explain
894 ]).
895
896explain -->
897 html([ div(class(smallprint),
898 [ p([ 'Actions such as rating, commenting and tagging ',
899 'requires you to be signed in. ',
900 'We use ', a(href('http://openid.net/'), 'OpenID'), '. ',
901 'Currently, we accept any OpenID provider. ',
902 'Tested with ', \openid_ok
903 ]),
904 p([ 'After logging in for the first time, we will ask for ',
905 'some additional information. All information is ',
906 'optional.'
907 ])
908 ])
909 ]).
910
911
915
916quick_buttons(Buttons) :-
917 findall(Img, quick_button(Img), Buttons).
918
919quick_button(img([ src(Icon),
920 href(Provider),
921 alt(Name),
922 title('Sign in with '+Name)
923 ])) :-
924 openid_provider(2, Provider, Name, ImgName),
925 http_absolute_location(icons(ImgName), Icon, []).
926
927openid_provider(2, LoginWithGoogle, 'Google', 'social_google_box.png') :-
928 http_link_to_id(login_with_google, [], LoginWithGoogle).
929openid_provider(2, 'http://me.yahoo.com', 'Yahoo', 'social_yahoo_box_lilac.png').
930openid_provider(1, 'https://openid.stackexchange.com/%user%', 'StackExchange', -).
931
932openid_ok -->
933 { Term = openid_provider(_Version, _URL, _Name, _Icon),
934 findall(Term, Term, Terms)
935 },
936 openid_ok(Terms).
937
938openid_ok([]) --> [].
939openid_ok([H|T]) -->
940 openid_ok1(H),
941 ( {T == []}
942 -> []
943 ; html(', '),
944 openid_ok(T)
945 ).
946
947openid_ok1(openid_provider(2, URL, Name, _Icon)) --> !,
948 html(a(href(URL), Name)).
949openid_ok1(openid_provider(1, URL, Name, _Icon)) --> !,
950 html([ Name, ' using the url ',
951 span(class('openid-url-pattern'), URL)
952 ]).
953
954
959
960verify_user(Request) :-
961 openid_verify([ ax([ email(_, [required]),
962 nickname(_),
963 fullname(_),
964 firstname(_),
965 lastname(_)
966 ])
967 ], Request).
968
969
970 973
974:- if(current_predicate(oauth_authenticate/3)). 975
976:- http_handler(root(user/login_with_google), login_with_google, []). 977
978:- setting(google:client_id, atom, '',
979 'Google project ClientID code'). 980:- setting(google:client_secret, atom, '',
981 'Google project ClientSecret code'). 982
986
987login_with_google(Request) :-
988 http_parameters(Request,
989 [ 'openid.return_to'(ReturnTo, [default(/)]),
990 stay(Stay, [default(false)])
991 ]),
992 oauth_authenticate(Request, 'google.com',
993 [client_data(_{return_to:ReturnTo, stay:Stay})]).
994
995:- multifile
996 google_client:key/2,
997 google_client:login_existing_user/1,
998 google_client:create_user/1. 999
1000google_client:key(client_id, ClientID) :-
1001 setting(google:client_id, ClientID).
1002google_client:key(client_secret, ClientSecret) :-
1003 setting(google:client_secret, ClientSecret).
1004
1009
1010google_client:login_existing_user(Claim) :-
1011 google_fake_open_id(Claim, GoogleID),
1012 site_user_property(_User, openid(GoogleID)), !,
1013 google_login(Claim).
1014google_client:login_existing_user(Claim) :-
1015 downcase_atom(Claim.get(email), ClaimedEmail),
1016 site_user_property(UUID, email(Email)),
1017 downcase_atom(Email, ClaimedEmail), !,
1018 debug(google, 'Found ~p with ~p', [UUID, Claim.email]),
1019 google_fake_open_id(Claim, GoogleID),
1020 set_user_property(UUID, openid(GoogleID)),
1021 google_login(Claim).
1022
1026
1027google_client:create_user(Profile) :-
1028 http_session_assert(ax(Profile)),
1029 google_login(Profile).
1030
1031google_login(Claim) :-
1032 http_open_session(_, []),
1033 google_fake_open_id(Claim, GoogleID),
1034 http_session_retractall(openid(_)),
1035 http_session_assert(openid(GoogleID)),
1036 http_current_request(Request),
1037 ( true(Claim.client_data.stay)
1038 -> debug(google, 'Stay signed in: ~p', [GoogleID]),
1039 http_openid:openid_hook(stay_signed_in(GoogleID))
1040 ; true
1041 ),
1042 http_redirect(moved_temporary, Claim.client_data.return_to, Request).
1043
1044google_fake_open_id(Claim, GoogleID) :-
1045 atomic_list_concat(['http://google.com/fake_open_id/', Claim.sub],
1046 GoogleID).
1047
1048true(true).
1049true(yes).
1050
1051:- endif. 1052
1053
1054 1057
1062
1063logout(Request) :-
1064 openid_logged_in(OpenId), !,
1065 openid_logout(OpenId),
1066 reply_html_page(
1067 user(logout),
1068 title('Logged out'),
1069 [ p('Thanks for using www.swi-prolog.org'),
1070 \logout_back_link(Request)
1071 ]).
1072logout(Request) :-
1073 reply_html_page(
1074 user(logout),
1075 title('Not logged in'),
1076 [ p(class(warning), 'You are not logged in'),
1077 \logout_back_link(Request)
1078 ]).
1079
1080
1081logout_back_link(Request) -->
1082 { http_parameters(Request,
1083 [ 'openid.return_to'(Return, [optional(true)])
1084 ]),
1085 nonvar(Return)
1086 }, !,
1087 html(p(['Go ', a(href(Return), back), '.'])).
1088logout_back_link(_) -->
1089 [].
1090
1091
1093
1094current_user -->
1095 current_user(default).
1096
1097current_user(Style) -->
1098 { Style \== create_profile,
1099 openid_logged_in(OpenID), !,
1100 ensure_profile(OpenID, User),
1101 ( site_user_property(User, name(Name)),
1102 Name \== ''
1103 -> Display = Name
1104 ; Display = OpenID
1105 ),
1106 http_link_to_id(view_profile, [], Profile)
1107 },
1108 html(div(class('current-user'),
1109 [ a([href(Profile)], Display),
1110 ' (', \logout_link, ')'
1111 ])).
1112current_user(Style) -->
1113 { Style \== create_profile,
1114 http_current_request(Request), !
1115 },
1116 html(div(class('current-user'),
1117 \login_link(Request))).
1118current_user(_Style) -->
1119 [].
1120
1124
1125login_link(Request) -->
1126 { ( memberchk(request_uri(Here), Request)
1127 -> Attrs = ['openid.return_to'(Here)]
1128 ; Attrs = []
1129 ),
1130 http_link_to_id(swipl_login, Attrs, Login)
1131 },
1132 html(a([class(signin), href(Login)], login)).
1133
1137
1138logout_link -->
1139 { http_link_to_id(logout, [], Logout) },
1140 html(a([href(Logout)], 'logout')).
1141
1142
1147
1148redirect_master(Request) :-
1149 option(host(Host), Request),
1150 server(_, Host),
1151 server(master, Master),
1152 Host \== Master, !,
1153 option(request_uri(URI), Request),
1154 format(string(To), 'https://~w~w', [Master, URI]),
1155 http_redirect(see_other, To, Request)