changeset 15:35bc4da563dd

Realms; documentation and license
author Adam Chlipala <adam@chlipala.net>
date Sun, 02 Jan 2011 11:22:30 -0500
parents 6b2a44da71b0
children 9851bc87b0d7
files LICENSE src/ur/openid.ur src/ur/openid.urs tests/test.ur
diffstat 4 files changed, 148 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE	Sun Jan 02 11:22:30 2011 -0500
@@ -0,0 +1,25 @@
+Copyright (c) 2010-2011, Adam Chlipala
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+- The names of contributors may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
--- a/src/ur/openid.ur	Sun Jan 02 10:33:07 2011 -0500
+++ b/src/ur/openid.ur	Sun Jan 02 11:22:30 2011 -0500
@@ -376,6 +376,10 @@
                     return (Some "Wrong return_to in OP response")
                 else
                     return None
+
+        val realmString = case r.Realm of
+                              None => ""
+                            | Some realm => "&openid.realm=" ^ realm
     in
         dy <- discover r.Identifier;
         case dy of
@@ -385,7 +389,7 @@
                 Stateless =>
                 redirect (bless (dy ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id="
                                  ^ r.Identifier ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to="
-                                 ^ show (effectfulUrl returnTo)))
+                                 ^ show (effectfulUrl returnTo) ^ realmString))
               | Stateful ar =>
                 assoc <- association ar.AssociationType ar.AssociationSessionType dy;
                 case assoc of
@@ -394,7 +398,7 @@
                   | Association assoc =>
                     redirect (bless (dy ^ "?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=checkid_setup&openid.claimed_id="
                                      ^ r.Identifier ^ "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.assoc_handle="
-                                     ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo)))
+                                     ^ assoc.Handle ^ "&openid.return_to=" ^ show (effectfulUrl returnTo) ^ realmString))
     end
 
 task periodic 1 = fn () =>
--- a/src/ur/openid.urs	Sun Jan 02 10:33:07 2011 -0500
+++ b/src/ur/openid.urs	Sun Jan 02 11:22:30 2011 -0500
@@ -1,16 +1,124 @@
+(* This module implements the key primitive of the OpenID 2.0 authentication
+ * protocol, as specified in:
+ *   http://specs.openid.net/auth/2.0
+ *
+ * Module author: Adam Chlipala
+ *
+ * Known missing features:
+ * - Compatibility with prior OpenID protocols
+ * - Endpoint discovery via XRIs or Yadis
+ * - Immediate requests (authentication with no opportunity for user
+ *   interaction)
+ * - Publishing information for relying party discovery
+ * - Extensions (in the protocol specification's terminology)
+ *
+ * Support for every other aspect of relying party functionality should be
+ * present, including appropriate security measures.
+ *)
+
+(* == Quick summary of OpenID ==
+ * There are lots of protocol details, but my take on it comes down to one
+ * simple idea.  OpenID exports a primitive that allows a web site that verify
+ * that a particular _user_ wishes to provide his identity to a particular
+ * _URL_.  There is use of cryptography and other fanciness to implement this
+ * primitive securely and efficiently, but application builders shouldn't need
+ * to think about the implementation details.  Indeed, that simple interface is
+ * what this module exports.  Thanks to Ur/Web's usual encapsulation guarantees,
+ * client code need not worry about accidentally disturbing state used by the
+ * protocol.
+ *
+ * The last key aspect is that URLs are used to identify users.  Each URL should
+ * point to an HTML page containing a special tag which points to another URL
+ * that is assigned responsibility for answering queries about what the user
+ * has authorized.  To use the library, you only need to think about the
+ * initial, user-identifying URLs, which form a kind of universal username
+ * namespace.
+ *)
+
+(* The protocol provides some options for how particular security requirements
+ * can be satisfied.  This module defines a few datatypes for the different
+ * dimensions of choice. *)
+
+(* First, we have association types, which are methods of guaranteeing message
+ * integrity.  The only options in OpenID 2.0 are two different hash-based
+ * message authentication codes (HMACs).  [HMAC_SHA256] is the stronger
+ * scheme. *)
+
 datatype association_type = HMAC_SHA1 | HMAC_SHA256
+
+(* Next, there are association session types, which are approaches to
+ * establishing shared secrets that can be used to provide integrity.  There are
+ * two versions of the Diffie-Hellman key exchange protocol, based on pairing
+ * with different MAC algorithms.  The [NoEncryption] option is only
+ * appropriate for communication via SSL, which already provides secrecy.  If
+ * [NoEncryption] is requested over an unencrypted HTTP connection, the library
+ * will automatically switch to [DH_SHA256]. *)
+
 datatype association_session_type = NoEncryption | DH_SHA1 | DH_SHA256
+
+(* Finally, you have the option to skip all this crypto stuff in your
+ * application, at some expense.  Use of cryptography with shared secrets allows
+ * you to authenticate a user with one fewer round-trip to the remote web server
+ * that is placed in charge of that user's identity.  This benefit is traded for
+ * greater space requirements in your application, with several kinds of data
+ * stored in local SQL tables, with the number of rows roughly proportional to
+ * the number of authentications, over short time periods.  A stateless approach
+ * uses local state only as a cache for predictable HTTP request results, with
+ * storage use proportional to the number of users.  (The stateful approach uses
+ * that same state and more.) *)
+
 datatype association_mode =
          Stateless
        | Stateful of {AssociationType : association_type,
                       AssociationSessionType : association_session_type}
 
-datatype authentication = AuthenticatedAs of string | Canceled | Failure of string
+(* An authentication attempt terminates in one of four ways.
+ * First, the user might get bored and surf away, never finishing the process.
+ * If so, your application will never be told explicitly.
+ * The other possibilities are captured by this datatype: *)
+
+datatype authentication =
+         AuthenticatedAs of string
+         (* Successful authentication, with the user's verified identifying
+          * URL *)
+       | Canceled
+         (* The user explicitly canceled the authentication process. *)
+       | Failure of string
+         (* Something went wrong, and here's some text that hopefully diagnoses
+          * the problem. *)
+
+(* Finally, here's the function to call to verify that a user wants to
+ * authenticate to your particular web application.  Note that this will only
+ * work properly if your project .urp file includes a 'prefix' directive that
+ * gives the full protocol, hostname, and port part of your URLs.
+ *
+ * If authentication proceeds successfully, the function will never return.
+ * Instead, the user is redirected to his identity provider, to authenticate with
+ * whatever method they have agreed on.  When that process finishes, the user is
+ * redirected back to your app, at which time the function that you pass as the
+ * first argument below is called with the result, to generate the page to show
+ * to the user.
+ *
+ * If authentication fails before the user is redirected away, the original
+ * function returns an error message suitable for display to technically-savvy
+ * users. *)
 
 val authenticate : (authentication -> transaction page)
                    -> {Association : association_mode,
-                       Identifier : string}
+                       (* Your preferences for statefulness and cryptography.
+                        * If the remote server doesn't support some kind of
+                        * crypto that you ask for, the library automatically
+                        * switches to a mode that the server advertises as
+                        * supported. *)
+                       Identifier : string,
+                       (* The URL that the user claims identifies him.
+                        * It may also point to a generic authentication service
+                        * that will take care of deciding the proper
+                        * username. *)
+                       Realm : option string
+                       (* A URL prefix that we are asking the user to authorize.
+                        * If provided, it must be a genuine prefix of every
+                        * application URL.  If omitted, we authorize for just one
+                        * specific URL, which is the authentication-specific URL
+                        * that the library always chooses automatically. *)}
                    -> transaction string
-(* Doesn't return normally if everything goes as planned.
- * Instead, the user is redirected to his OP to authenticate there.
- * Later, the function passed as the first argument should be called with the result. *)
--- a/tests/test.ur	Sun Jan 02 10:33:07 2011 -0500
+++ b/tests/test.ur	Sun Jan 02 11:22:30 2011 -0500
@@ -7,9 +7,10 @@
 
 fun auth r =
     msg <- Openid.authenticate afterward
-                               {Association = Openid.Stateless (* Openid.Stateful {AssociationType = Openid.HMAC_SHA256,
-                                                                                   AssociationSessionType = Openid.NoEncryption} *),
-                                Identifier = r.Id};
+                               {Association = Openid.Stateful {AssociationType = Openid.HMAC_SHA256,
+                                                               AssociationSessionType = Openid.NoEncryption},
+                                Identifier = r.Id,
+                                Realm = Some "http://localhost:8080/"};
     error <xml>{[msg]}</xml>
 
 fun main () = return <xml><body>