Access control == Description == This tutorial shows how accesses can be controlled in HOP. It teaches how to protect local resources and how to request authentication from clients. This tutorial mainly shows how to use the facilities described in the ,( ( "Access Control") "access.wiki") API page. == Users == The ,( "RC file" "07-hoprc.wiki") that is loaded on startup is the place where to declare users. Users are declared by the means of the ++add-user!++ function. A function may be granted the permission to read directories and to execute services. A user is refereed to by a name. It is authenticated by a password. It carries attributes that specify his granted authorizations. User passwords are generated by the ++[[password]]++ weblet. ~~ Note: ,( :style { font-weight: bold; color: red } [ For security reasons, users can only be defined in the ,( "RC file" "07-hoprc.wiki"). Users can no longer be added as soon as the first Hop request has been intercepted.]) ~~ Let us suppose that it is needed to create an authentication for a user named //Turing// whose password is //enigma//. Executing the ++[[password]]++ will deliver the key ++7cfc0ea2b67e6d80366f014674cb6cd9++. Let us assume that this user is granted the permission to execute a weblet named ++crypt++ and to visit the directory named ++/university/manchester++. Then, the HOP ,( "RC file" "07-hoprc.wiki") should contain an expression such as: (add-user! "turing" :password "+7cfc0ea2b67e6d80366f014674cb6cd9" :directories '("/university/manchester") :services '(enigma)) ~~ When HOP authenticates the requests it intercepts. For that, it uses three complementary techniques. - The first one uses an extension of URLs. The host component of a URL may be augmented with a //user authentication//. The syntax of this extension is the following: the host destination is prefixed with ++:@++. For instance, when the HTTP request: ++http://user:456789@foo.nowhere.org/tmp++ is intercepted by HOP, the following authenticate the request as belonging to user named ++user++, whose unencrypted password is ++456789++. - The header of an [[http://www.w3.org/Protocols/rfc2616/rfc2616.html|HTTP]] request may contain two authentication fields: * authorization: * proxy-authorization: these contain unencrypted user authentication. - //Cookies// may hold users authentication. The authenticated user is stored in the ++user++ field of the instance of the class ::http-request that represents the request. It can be used by service for implementing access restriction. == The anonymous user == Each weblet may implement its own access restriction policy which is based on request authentication. When no authentication is found Hop attributes the request to a fake //anonymous// user. Thus, in order to find out is the request is authorized, Hop checks if the //anonymous// user is allowed to execute it. ~~ At the beginning of its execution, Hop creates an anonymous user whose access permissions are specified in system the ++hoprc.hop++ file. This settings might change from version to another but in general the anonymous user is only allowed to access the Hop ++[[doc]]++, ++[[password]]++, and ++[[home]]++ weblets. ~~ Defining a custom //anonymous// user is the standard way for specifying access permission to un-authenticated user. If one needs to grant access to its own weblet ++foo++. One might add to his ++hoprc.hop++ the following declaration: (add-user! "anonymous" :directories (cons "" (hop-path)) :services '(foo)) ~~ In order to disable default access restriction (that is, in order to make all weblets, **by default**, accessible), one may grant privileged authorization to the anonymous user with the following declaration: (add-user! "anonymous" :directories '* :services '*) == Implementing access restriction in services == Let's imagine a weblet ++weblet-1++ that restricts its execution to users granted the permission to execute a service named ++service-1++. A possible implementation of that weblet could be: (define-service (weblet-1) (if (authorized-service? (current-request) 'service-1) ... the actual implementation ... (user-access-denied (current-request)))) If the authentication fails, the server sends back a //request for authentication// to the client. The general behavior of web browser when intercepting such requests is to pop up a window prompting a user and a password and to send back this information to the server. == Implementing authentication with cookies == More elaborated access controls can be programmed with //cookies//. For the sake of the example, let us study the implementation of the authentication of ,( :href (format "http://localhost:~a/hop/hz" (hop-port)) ( :style "font-variant: small-caps" "Hz")) weblets manager. ~~ Here is the declaration of the ++hz++ service: (define-service (hz) (let ((req (current-request))) (if (users-added?) (let ((user (hz-get-access req))) (if (and (user? user) (authorized-service? req 'hz)) (hz-main req user) (hz-authenticate req))) (hz-main req #f 'local)))) When users have been added, the main ,( :style "font-variant: small-caps" "Hz") function invokes the function ++hz-get-access++ that checks if the received request contains user authentication. Its implementation is: (define (hz-get-access req) (let ((cookie (http-cookie-get req "hop_hz"))) (if (string? cookie) (let ((exp (with-input-from-string (base64-decode cookie) read))) (and (pair? exp) (find-user-cookie exp)))))) That function searches the request ++req++ in order to find the value of the cookie named ++hop_hz++. When this cookie is found, ++hz-get-access++ parses its value using the regular ++read++ function. This returns a list that is inspected with the function ++find-user-cookie++. Its definition is: (define (find-user-cookie exp) (let* ((user (memq :user exp)) (passwd (memq :passwd exp))) (and (pair? user) (pair? (cdr user)) (symbol? (cadr user)) (pair? passwd) (pair? (cdr passwd)) (symbol? (cadr passwd)) (find-user/encrypt (symbol->string (cadr user)) (symbol->string (cadr passwd)) hz-encrypt-password)))) An encrypted password is contained in that list. It is associated with the keyword :passwd. Because that password can be read by anyone that has access to the cookies stored on the client that has emitted the request, HOP restrict the extent of the password. The password is encoded by the function ++hz-encrypt-password++ whose implementation is: (define (hz-encrypt-password p) (string-append (hz-session-key) p)) Because password are encrypted, if that cookie is stolen by a malicious user, it cannot be used on another instance of HOP and it is valid until the ++hz-session-key++ changes. This is up to the ,( :style "font-variant: small-caps" "Hz") to change its key frequently enough. For instance, it may change it when the number of requests handled by HOP exceeded a limit. ~~ The function ++login++ is used to let users log in. Its implementation is: (define (login req::http-request name pass local) ~(let ((svc ($hz/setcookie $(begin name).value $(begin pass).value (if $(begin local).checked "local" "share"))) (cback (lambda (http) (let ((date (new Date))) (date.setMinutes (+ (date.getMinutes) $(hz-auto-logout-minute))) (cookie-set! "hop_hz" http $req.path #f date) (set! location $req.path))))) (with-hop svc cback))) It relies on the service ++hz/setcookie++ that stores a cookie on the client. That cookie will be automatically transmitted to server by client on further requests to ,( :style "font-variant: small-caps" "Hz"). Its implementation is: (define-service (hz/setcookie name passwd mode) (let* ((pd (hz-encrypt-password (md5sum (string-append name " " passwd)))) (cookie `(user: ,name passwd: ,pd mode: ,mode)) (p (open-output-string))) (display cookie p) (let ((s (close-output-port p))) (instantiate::http-response-string (body (base64-encode s)))))) In order to log out, users use: (define (logout req::http-request) (let ((path req.path)) ~(let ((path $path)) (cookie-remove! "hop_hz" path) (set! location path)))) == Granting access globally == Access can be granted //globally// using the ++hop-path-access-control++ and ++hop-service-access-control++ parameters. These two parameters return procedure acception two parameters: - the current request (++current-request++), - either a string denoting the path ++hop-path-access-control++ or a symbol denoting the service name of ++hop-service-access-control++. The function ++authorized-path?++ checks is a path can be served. In order to take its decision, this function first checks if: - there is an authenticated user associated with the request, - if that user is granted the permission to access the file, - if one the groups the user belongs to is granted the permission to access the file. If this first test fails, the procedure of ++hop-path-access-control++ is invoked in order to check if the file is //globally// authorized. The function ++authorized-service?++ acts in a similar way for executing services. ~~ For the sake of the example, let us consider a HOP sessions that grants access to the directory ++/tmp++ for all requests event those were no user is authenticated. This can be implemented as: (hop-path-access-control-set! (lambda (req path) (substring-at? path "/tmp" 0))) ~~ We can modify the previous example for granting access to ++/tmp++ only for requests associated with an authenticated user: (hop-path-access-control-set! (lambda (req::http-request path) (and (substring-at? path "/tmp" 0) (user? req.user)))) == Enabling proxy requests == In addition to act as a server, the Hop broker can also play the role of a Web proxy. This section explains how to configure it for enabling proxy requests. * The parameter ++hop-proxy-allow-remote-client++ controls whenever the Hop broken may be or may not be used as proxy for non local requests. That is, when set to ++#f++, only proxy requests emitted from the host (the computer) that is actually running the broker are authorized. Proxy requests from remote hosts are rejected. * The parameter ++hop-proxy-remote-authentication++ is used when ++hop-proxy-allow-remote-client++ is set to ++#t++. It allows proxy requests from remote hosts only for authenticated used. Setting it to ++#f++ allows everybody on earth to use the broker as a regular web proxy. * The parameter ++hop-proxy-authentication++ controls whenever local proxy request should be authenticated. Setting this parameter to ++#f++ let every one on the host running Hop to use it as a regular web proxy. * When proxy request must be authenticated, only used provided with the permission to execute the ++proxy++ service can use the broker as web proxy. === Example 1 === Forbidding remote clients to use Hop as proxy and permitting all local clients to use it can be done with: (hop-proxy-allow-remote-client-set! #f) (hop-proxy-authentication-set! #f) === Example 2 === Permitting the user //Doe// to use the broker as a proxy from everywhere on earth can be done with: (hop-proxy-allow-remote-client-set! #t) (hop-proxy-remote-authentication-set! #t) (hop-proxy-authentication-set! #t) (add-user! "doe" :password "XXXXXXXX" :services '(proxy))