View source with raw comments or as raw
    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_login,
   37          [ login_button//1,            % +Options
   38            login_continue_button//0,
   39            reply_logged_in/1,          % +Options
   40            reply_logged_in_page/1,     % +Options
   41            reply_logged_out/1,         % +Options
   42            reply_logged_out_page/1,    % +Options
   43            current_user_info/2         % +Request, -UserInfo
   44          ]).   45:- use_module(library(http/http_dispatch)).   46:- use_module(library(http/http_parameters)).   47:- use_module(library(http/http_json)).   48:- use_module(library(http/html_write)).   49:- use_module(library(http/js_write)).   50:- use_module(library(option)).   51:- use_module(library(apply)).   52:- use_module(library(broadcast)).   53
   54:- use_module('../config', []).   55
   56:- multifile
   57    swish_config:login/2,
   58    swish_config:login_item/2,
   59    swish_config:li_login_button//1,    % +Options
   60    swish_config:reply_logged_in/1,     % +Options
   61    swish_config:reply_logged_out/1,    % +Options
   62    swish_config:user_info/3,           % +Request, -Server, -Info
   63    swish_config:user_profile/2.        % +Request, -Info

SWISH login support

This module provides the generic code to deal with optional login using multiple protocols. Optional means that SWISH may be used both anonymously and after login.

This module cooperates with web/js/login.js. Login providers are defined using configuration hooks. The various login options are accompagnied by configuration files in config-available. */

   76:- http_handler(swish(login),        swish_login,  [id(login)]).   77:- http_handler(swish(user_info),    user_info,    [id(user_info)]).   78
   79
   80		 /*******************************
   81		 *          UI ELEMENTS		*
   82		 *******************************/
 swish_config:li_login_button(+Options)//
Hook called from page.pl to include the login buttons.
   88swish_config:li_login_button(Options) -->
   89    html(li(\login_button(Options))).
 login_button(+Options)//
Add a login/logout button. This button is added if there is at least one option for optional login.
   96login_button(_Options) -->
   97    { findall(Item, login_item(Item), Items0),
   98      Items0 \== [],
   99      sort(Items0, Items),
  100      http_link_to_id(login, [], Login)
  101    },
  102    !,
  103    html(a([ href(Login), id(login), class(login) ],
  104           [ span(class(login),
  105                  \login_items(Items)),
  106             span([ class(logout)
  107                  ],
  108                  [ span(class(value), 'Logout')
  109                  ])
  110           ])).
  111login_button(_Options) -->              % config-available/auth_http_always.pl
  112    html(a([ id(login), class([login, 'no-logout']) ],
  113           [ span([ class(logout)
  114                  ],
  115                  [ span(class(value), [])
  116                  ])
  117           ])).
  118
  119login_item(item(Tag, Server, Item)) :-
  120    swish_config:login_item(Server, Item0),
  121    (   Item0 = Tag-Item
  122    ->  true
  123    ;   Item = Item0,
  124        Tag = 0
  125    ).
 login_items(+Items)
Show the login options. If there is only one, we just show a login field.
  132login_items([item(_Tag, Server, Item)]) -->
  133    !,
  134    { findall(Attr, login_attr(Item, Attr), Attrs)
  135    },
  136    html(span(['data-server'(Server)|Attrs],
  137              [ span(class([glyphicon, 'glyphicon-log-in']), []),
  138                span(class(value), 'Login')
  139              ])).
  140login_items(Items) -->
  141    { maplist(arg(3), Items, HTML) },
  142    html([ span(class(value), HTML)
  143         ]).
  144
  145login_attr(Item, 'data-frame'(Frame)) :-
  146    sub_term('data-frame'(Frame), Item).
 reply_logged_in(+Options) is det
 reply_logged_in_page(+Options) is det
Reply with an HTML document that the login succeeded. This is normally called from the protocol-specific login handler to indicate that the login succeeded. Options:
identity_provider(+Provider)
Indicate the identity provider that did the login. Provider is a term for html//1.
user(+User)
User id of the identified user.
name(+Name)
Common name of the identified user.
user_info(+Dict)
Information provided by the identity provider.

At least one of user(User) or name(Name) must be present.

The predicate reply_logged_in/1 calls the hook reply_logged_in/1. This hook is provided for interacting with a user profile manager.

  174reply_logged_in(Options) :-
  175    swish_config:reply_logged_in(Options),
  176    !.
  177reply_logged_in(Options) :-
  178    reply_logged_in_page(Options).
  179
  180reply_logged_in_page(Options) :-
  181    reply_html_page(
  182        title('Logged in'),
  183        [ h4('Welcome'),
  184          p([ 'You have been identified ',
  185              \identity_provider(Options),
  186              ' as ',
  187              \user(Options)
  188            ]),
  189          \login_continue_button
  190        ]).
  191
  192identity_provider(Options) -->
  193    { option(identity_provider(Provider), Options) },
  194    !,
  195    html(['by ', Provider]).
  196identity_provider(_) --> [].
  197
  198user(Options) -->
  199    { option(user(User), Options) },
  200    !,
  201    html(User),
  202    (   { option(name(Name), Options) }
  203    ->  html([' (', Name, ')' ])
  204    ;   []
  205    ).
  206user(Options) -->
  207    { option(name(Name), Options) },
  208    !,
  209    html(Name).
  210user(_) -->
  211    html(unknown).
 login_continue_button//
The login page is opened either inside an iframe inside a SWISH modal dialog or inside a browser popup window. This scripts adds a button to dismiss the browser popup window.
  219login_continue_button -->
  220    html(style(\[ 'div.login-continue { text-align: center; margin-top: 2em; }'
  221                ])),
  222
  223    js_script({|javascript||
  224function inIframe() {
  225  try {
  226    return window.self !== window.top;
  227  } catch (e) {
  228    return true;
  229  }
  230}
  231
  232function append( elString, parent ) {
  233  var div = document.createElement( "div" );
  234  div.innerHTML = elString;
  235  document.querySelector( parent || "body" ).appendChild( div.firstChild );
  236}
  237
  238if ( !inIframe() ) {
  239  append('<div class="login-continue">\n'+
  240         '  <button onclick="window.close()">\n'+
  241         '    Continue\n'+
  242         '  </button>\n'+
  243         '</div>');
  244}
  245              |}).
 reply_logged_out(+Options)
Perform pluggable logout
  253reply_logged_out(Options) :-
  254    swish_config:reply_logged_out(Options),
  255    !.
  256reply_logged_out(Options) :-
  257    reply_logged_out_page(Options).
  258
  259reply_logged_out_page(Options) :-
  260    option(reply(Format), Options, json),
  261    (   Format == json
  262    ->  reply_json_dict(true)
  263    ;   true
  264    ).
  265
  266
  267		 /*******************************
  268		 *          HTTP HANDLERS	*
  269		 *******************************/
 swish_login(+Request)
HTTP handler that deals with login. This handler is called from web/js/login.js which adds the selected login server from the data-server attribute.
  277swish_login(Request) :-
  278    http_parameters(Request,
  279                    [ server(Server, [default(default)])
  280                    ]),
  281    swish_config:login(Server, Request).
 user_info(+Request)
HTTP handler to obtain information on the currently logged in user. This handler tries the clauses dealing with login for a specific protocol. This is called by login.update() from login.js.
  289user_info(Request) :-
  290    http_parameters(Request,
  291                    [ reason(Reason, [optional(true)])
  292                    ]),
  293    (   current_user_info(Request, Info)
  294    ->  reply_json_dict(Info)
  295    ;   (   Reason == logout_by_http
  296        ->  broadcast(swish(logout(http)))
  297        ;   true
  298        ),
  299        reply_json_dict(null)
  300    ).
 current_user_info(+Request, -Info) is semidet
If there is a logged in user, Info is a dict with information about this user.
  307current_user_info(Request, Info) :-
  308    swish_config:user_info(Request, _Server, UserInfo),
  309    (   swish_config:user_profile(Request, Profile)
  310    ->  copy_fields([identity_provider, auth_method, logout_url],
  311                    UserInfo, Profile, Info)
  312    ;   Info = UserInfo
  313    ).
  314
  315copy_fields([], _From, Dict, Dict).
  316copy_fields([H|T], From, Dict0, Dict) :-
  317    (   V = From.get(H)
  318    ->  copy_fields(T, From, Dict0.put(H,V), Dict)
  319    ;   copy_fields(T, From, Dict0, Dict)
  320    )