r20600: Web Application Framework
authorDerrell Lipman <derrell@samba.org>
Sun, 7 Jan 2007 23:06:50 +0000 (23:06 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 19:37:13 +0000 (14:37 -0500)
- Add authentication.  The Web Application Framework can now be called
  directly and it will rqeuire authentication if required, and should re-query
  the user to log in when the session expires.

- General clean-up

12 files changed:
services/json_auth.esp
services/request.esp
services/resources.esp
services/samba/system.esp
webapps/scripting/common.js
webapps/scripting/preauth.esp
webapps/swat/source/class/swat/main/AbstractModuleFsm.js
webapps/swat/source/class/swat/main/Authenticate.js [new file with mode: 0644]
webapps/swat/source/class/swat/main/Main.js
webapps/swat/source/class/swat/module/ldbbrowse/Fsm.js
webapps/swat/source/class/swat/module/statistics/Fsm.js
webapps/swat/source/class/swat/module/statistics/Gui.js

index 0fdd98037d6ab46d9ddf6c3b2d508c12bb0bb9c5..57fbd7aaacda0a48652a445100800b5379c6dae7 100644 (file)
@@ -1,13 +1,67 @@
 <%
+libinclude("auth.js");
+
 /* Return true to allow access; false otherwise */
-function json_authenticate(serviceComponents, method, scriptTransportId)
+function json_authenticate(serviceComponents, method, scriptTransportId, error)
 {
-    // Don't allow any access via ScriptTransport, for now.
+    // Don't allow any access via ScriptTransport, for now.  There are serious
+    // potential security exploits that will need to be protected against when
+    // we do want to allow use of ScriptTransport.  -- djl
     if (scriptTransportId != jsonrpc.Constant.ScriptTransport.NotInUse)
     {
+        error.setError(jsonrpc.Constant.ServerError.PermissionDenied,
+                       "Permission denied");
+        return false;
+    }
+
+    // Does the requested method require authentication?
+    if (! _authentication_required(serviceComponents, method))
+    {
+        // Nope.  Let 'em in.
+        return true;
+    }
+
+    // Did our session expire?
+    if (request['SESSION_EXPIRED'] == "True")
+    {
+        // Yup.
+        error.setError(jsonrpc.Constant.ServerError.SessionExpired,
+                       "Session expired");
+        error.setInfo(getDomainList());
+        return false;
+    }
+
+    // Are we authenticated?
+    if (! session.AUTHENTICATED)
+    {
+        // Nope.
+        error.setError(jsonrpc.Constant.ServerError.NotLoggedIn,
+                       "Not logged in");
+        error.setInfo(getDomainList());
+        return false;
+    }
+
+    return true;
+}
+
+
+/*
+ * Return true if authentication is required for the specified method;
+ * false otherwise.
+ */
+function _authentication_required(serviceComponents, method)
+{
+    var m = join(".", serviceComponents) + "." + method;
+
+    // See if this method requires authentication
+    if (m == "samba.system.login" ||
+        m == "samba.system.logout")
+    {
+        // Nope.
         return false;
     }
 
+    // Anything not listed above requires authentication
     return true;
 }
 
index 6f7e61e6e4e1b14908f84cb43756cee91d9ebb47..ae106be8ea9e10d8e71f9f30b7d6d8501c550179 100644 (file)
@@ -13,7 +13,6 @@
  * This is a simple JSON-RPC server.
  */
 
-
 /* Bring in the json format/parse functions */
 jsonrpc_include("json.esp");
 
@@ -30,6 +29,10 @@ string_init(global);
 /* Bring the system functions into the global frame */
 sys_init(global);
 
+/* Bring the session functions into the global frame */
+system_session(global);
+
+
 function printf()
 {
        print(vsprintf(arguments));
@@ -44,7 +47,7 @@ function printf()
 jsonrpc = new Object();
 jsonrpc.Constant = new Object();
 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
-jsonrpc.Constant.ErrorCode = new Object();   /* server-generated error codes */
+jsonrpc.Constant.ServerError = new Object(); /* server-generated error codes */
 jsonrpc.method = new Object();       /* methods available in requested class */
 
 /*
@@ -74,7 +77,7 @@ jsonrpc.Constant.ErrorOrigin.Client              = 4;
  * The default error code, used only when no specific error code is passed to
  * the JsonRpcError constructor.  This code should generally not be used.
  */
-jsonrpc.Constant.ErrorCode.Unknown               = 0;
+jsonrpc.Constant.ServerError.Unknown               = 0;
 
 /**
  * Error code, value 1: Illegal Service
@@ -82,14 +85,14 @@ jsonrpc.Constant.ErrorCode.Unknown               = 0;
  * The service name contains illegal characters or is otherwise deemed
  * unacceptable to the JSON-RPC server.
  */
-jsonrpc.Constant.ErrorCode.IllegalService        = 1;
+jsonrpc.Constant.ServerError.IllegalService        = 1;
 
 /**
  * Error code, value 2: Service Not Found
  *
  * The requested service does not exist at the JSON-RPC server.
  */
-jsonrpc.Constant.ErrorCode.ServiceNotFound       = 2;
+jsonrpc.Constant.ServerError.ServiceNotFound       = 2;
 
 /**
  * Error code, value 3: Class Not Found
@@ -99,14 +102,14 @@ jsonrpc.Constant.ErrorCode.ServiceNotFound       = 2;
  * detailed than "Method Not Found", but that error would always also be legal
  * (and true) whenever this one is returned. (Not used in this implementation)
  */
-jsonrpc.Constant.ErrorCode.ClassNotFound         = 3; // not used in this implementation
+jsonrpc.Constant.ServerError.ClassNotFound         = 3;
 
 /**
  * Error code, value 4: Method Not Found
  *
  * The method specified in the request is not found in the requested service.
  */
-jsonrpc.Constant.ErrorCode.MethodNotFound        = 4;
+jsonrpc.Constant.ServerError.MethodNotFound        = 4;
 
 /*
  * Error code, value 5: Parameter Mismatch
@@ -118,7 +121,7 @@ jsonrpc.Constant.ErrorCode.MethodNotFound        = 4;
  * This error is also used to indicate an illegal parameter value, in server
  * scripts.
  */
-jsonrpc.Constant.ErrorCode.ParameterMismatch     = 5;
+jsonrpc.Constant.ServerError.ParameterMismatch     = 5;
 
 /**
  * Error code, value 6: Permission Denied
@@ -129,23 +132,50 @@ jsonrpc.Constant.ErrorCode.ParameterMismatch     = 5;
  * authentication.  If the caller has not properly authenticated to use the
  * requested method, this error code is returned.
  */
-jsonrpc.Constant.ErrorCode.PermissionDenied      = 6;
+jsonrpc.Constant.ServerError.PermissionDenied      = 6;
+
+/*** Errors generated by this server which are not qooxdoo-standard ***/
 
 /*
- * Error code, value 7: Unexpected Output
+ * Error code, value 1000: Unexpected Output
  *
  * The called method illegally generated output to the browser, which would
  * have preceeded the JSON-RPC data.
  */
-jsonrpc.Constant.ErrorCode.UnexpectedOutput      = 7;
+jsonrpc.Constant.ServerError.UnexpectedOutput      = 1000;
 
 /*
- * Error code, value 8: Resource Error
+ * Error code, value 1001: Resource Error
  *
  * Too many resources were requested, a system limitation on the total number
  * of resources has been reached, or a resource or resource id was misused.
  */
-jsonrpc.Constant.ErrorCode.ResourceError         = 8;
+jsonrpc.Constant.ServerError.ResourceError         = 1001;
+
+/*
+ * Error code, value 1002: Not Logged In
+ *
+ * The user has logged out and must re-authenticate, or this is a brand new
+ * session and the user must log in.
+ *
+ */
+jsonrpc.Constant.ServerError.NotLoggedIn           = 1002;
+
+/*
+ * Error code, value 1003: Session Expired
+ *
+ * The session has expired and the user must re-authenticate.
+ *
+ */
+jsonrpc.Constant.ServerError.SessionExpired        = 1003;
+
+/*
+ * Error code, value 1004: Login Failed
+ *
+ * An attempt to log in failed.
+ *
+ */
+jsonrpc.Constant.ServerError.LoginFailed           = 1004;
 
 
 
@@ -250,6 +280,14 @@ function _JsonRpcError_create(origin, code, message)
     }
     o.setScriptTransportId = _setScriptTransportId;
 
+    function _setInfo(info)
+    {
+        // Add the info field only if info is actually provided.
+        // This is an extension to qooxdoo's normal Error return value.
+        this.data.info = info;
+    }
+    o.setInfo = _setInfo;
+
     function _Send()
     {
         var error = this;
@@ -276,7 +314,7 @@ var jsonInput = null;
 
 /* Allocate a generic error object */
 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
-                            jsonrpc.Constant.ErrorCode.Unknown,
+                            jsonrpc.Constant.ServerError.Unknown,
                             "Unknown error");
 
 /* Assume (default) we're not using ScriptTransport */
@@ -329,7 +367,7 @@ var nameFirstLetter =
     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
 /*
- * Ensure the method name is kosher.  A meethod name should be:
+ * Ensure the method name is kosher.  A method name should be:
  *
  *   - first character is in [a-zA-Z] 
  *   - other characters are in [_a-zA-Z0-9]
@@ -339,7 +377,7 @@ var nameFirstLetter =
 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
 {
     /* There's some illegal character in the service name */
-    error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
+    error.setError(jsonrpc.Constant.ServerError.MethodNotFound,
                    "Illegal character found in method name.");
     error.Send();
     return;
@@ -348,7 +386,7 @@ if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
 /* Now ensure that it begins with a letter */
 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
 {
-    error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
+    error.setError(jsonrpc.Constant.ServerError.MethodNotFound,
                    "The method name does not begin with a letter");
     error.Send();
     return;
@@ -366,7 +404,7 @@ if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
 {
     /* There's some illegal character in the service name */
-    error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
+    error.setError(jsonrpc.Constant.ServerError.IllegalService,
                    "Illegal character found in service name.");
     error.Send();
     return;
@@ -381,7 +419,7 @@ if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
  */
 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
 {
-    error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
+    error.setError(jsonrpc.Constant.ServerError.IllegalService,
                    "Illegal use of two consecutive dots in service name");
     error.Send();
     return;
@@ -395,7 +433,7 @@ for (var i = 0; i < serviceComponents.length; i++)
 {
     if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
     {
-        error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
+        error.setError(jsonrpc.Constant.ServerError.IllegalService,
                        "A service name component does not begin with a letter");
         error.Send();
         return;
@@ -413,7 +451,7 @@ var servicePath = join("/", serviceComponents) + ".esp";
 if (jsonrpc_include(servicePath))
 {
     /* Couldn't find the requested service */
-    error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
+    error.setError(jsonrpc.Constant.ServerError.ServiceNotFound,
                    "Service class `" + servicePath + "` does not exist.");
     error.Send();
     return;
@@ -451,7 +489,7 @@ if (valid)
 
 if (! valid)
 {
-    error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
+    error.setError(jsonrpc.Constant.ServerError.MethodNotFound,
                    "Method `" + jsonInput.method + "` not found.");
     error.Send();
     return;
@@ -467,10 +505,9 @@ if (! valid)
  */
 if (! json_authenticate(serviceComponents,
                         jsonInput.method,
-                        scriptTransportId))
+                        scriptTransportId,
+                        error))
 {
-    error.setError(jsonrpc.Constant.ErrorCode.PermissionDenied,
-                   "Permission denied");
     error.Send();
     return;
 }
index d491ed5701c573464c90669f14870794feaf9b29..e7fd164c34a6bf9e7dee29a7e58a80186d9e341c 100644 (file)
@@ -39,7 +39,7 @@ function _resourcesCreate()
         {
             /* Yup. */
             error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
-            error.setError(jsonrpc.Constant.ErrorCode.ResourceError,
+            error.setError(jsonrpc.Constant.ServerError.ResourceError,
                            "Session limit on resources (" +
                            RESOURCE_LIMIT +
                            ") exceeded.");
@@ -79,7 +79,7 @@ function _resourcesCreate()
         {
             /* Nope. */
             error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
-            error.setError(jsonrpc.Constant.ErrorCode.ResourceError,
+            error.setError(jsonrpc.Constant.ServerError.ResourceError,
                            "Resource not found.");
             return error;
         }
@@ -130,7 +130,7 @@ function _resourcesCreate()
         {
             /* Nope. */
             error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
-            error.setError(jsonrpc.Constant.ErrorCode.ResourceError,
+            error.setError(jsonrpc.Constant.ServerError.ResourceError,
                            "Resource not found.");
             return error;
         }
index e75151e93b5c0200021abc5f60f6c1117176531b..59844cda39895b8fed71013b5f67841dbfcaf45d 100644 (file)
 jsonrpc_include("resources.esp");
 
 
+/**
+ * Authenticate and log in
+ *
+ * @param params[0]
+ *   User name
+ *
+ * @param params[1]
+ *   Password
+ *
+ * @param params[2]
+ *   Domain
+ *
+ * @param error
+ *   An object of class JsonRpcError.
+ *
+ * @return
+ *   Success: "Logged in"
+ *   Failure: error event, origin=Server, code=LoginFailed
+ */
+function _login(params, error)
+{
+    var ret;
+    var creds = credentials_init();
+
+    creds.set_username(params[0]);
+    creds.set_password(params[1]);
+    creds.set_domain(params[2]);
+    creds.set_workstation(request['REMOTE_HOST']);
+    auth = userAuth(creds, request['REMOTE_SOCKET_ADDRESS']);
+
+    if (auth == undefined)
+    {
+        error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
+        error.setError(jsonrpc.Constant.ServerError.LoginFailed,
+                       "Invalid login.");
+        ret = error;
+    }
+    else if (auth.result)
+    {
+        session.AUTHENTICATED = true;
+        session.authinfo = new Object();
+
+        session.authinfo.username = auth.username;
+        session.authinfo.domain = auth.domain;
+        session.authinfo.credentials = creds;
+        session.authinfo.session_info = auth.session_info;
+
+        ret = "Logged in";
+    }    
+    else if (auth.report == undefined)
+    {
+        error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
+        error.setError(jsonrpc.Constant.ServerError.LoginFailed,
+                       "Login failed.");
+        ret = error;
+    }
+    else
+    {
+        error.setOrigin(jsonrpc.Constant.ErrorOrigin.Server);
+        error.setError(jsonrpc.Constant.ServerError.LoginFailed,
+                       "Login failed: " + auth.report);
+        ret = error;
+    }
+
+    return ret;
+}
+jsonrpc.method.login = _login;
+
+
+
+/**
+ * Retrieve the list of open resources (for debugging)
+ *
+ * @return "Logged out"
+ */
+function _logout(params, error)
+{
+    session.AUTHENTICATED = false;
+    return "Logged out";
+}
+jsonrpc.method.logout = _logout;
+
+
+
 /**
  * Retrieve the list of open resources (for debugging)
  */
index fe25287a74c1aa6b434db4ba2731ca9fdcfe51c1..c111089bd877df9028712f8f9a271b1d9429dadb 100644 (file)
@@ -68,34 +68,6 @@ function page_footer() {
 }
 
 
-/*
-  check if a uri is one of the 'always allowed' pages, even when not logged in
-  This allows the login page to use the same style sheets and images
-*/
-function always_allowed(uri) {
-       var str = string_init();
-
-        /* allow jsonrpc-based applications to do their own authentication */
-        var s = str.split('/', uri);
-        if (s[0] == "" && s[1] == 'apps') {
-                return true;
-        }
-
-       var s = str.split('.', uri);
-       if (s.length < 2) {
-               return false;
-       }
-
-       var ext = s[s.length-1];
-       var allowed = new Array("ico", "gif", "png","css", "js");
-       for (i in allowed) {
-               if (allowed[i] == ext) {
-                       return true;
-               }
-       }
-       return false;
-}
-
 /*
   display a table element
 */
index 489f6b5004b0f1067e233352aac932dbe42c7970..e6d04faf8d9abf0faf946b2c01af0b0e3ca3439c 100644 (file)
@@ -5,6 +5,36 @@ include("/scripting/common.js");
    output at all then that output is returned and the requested page
    is not given or processed.
 */ 
+
+/*
+  check if a uri is one of the 'always allowed' pages, even when not logged in
+  This allows the login page to use the same style sheets and images
+*/
+function always_allowed(uri) {
+       var str = string_init();
+
+        /* allow jsonrpc-based applications to do their own authentication */
+        var s = str.split('/', uri);
+        if (s[0] == "" && s[1] == 'index.html') {
+                return true;
+        }
+
+       var s = str.split('.', uri);
+       if (s.length < 2) {
+               return false;
+       }
+
+       var ext = s[s.length-1];
+       var allowed = new Array("ico", "gif", "png","css", "js");
+       for (i in allowed) {
+               if (allowed[i] == ext) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+
 if (server['SERVER_PROTOCOL'] == "http" &&
     server['TLS_SUPPORT'] == "True") {
        write("redirect to https");
@@ -14,4 +44,5 @@ if (server['SERVER_PROTOCOL'] == "http" &&
        /* present the login page */
        include("/login.esp");
 }
+
 %>
index 49222d90d435f2cd5a74c606bba34a14a13bca0f..fed11eb0d376426629555fabc4487104d25770c1 100644 (file)
@@ -109,12 +109,25 @@ qx.Proto.addAwaitRpcResultState = function(module)
       },
 
       "onentry" :
-        function(fsm, state)
+        function(fsm, event)
         {
-          // If we're coming from some other state...
-          if (fsm.getPreviousState() != "State_AwaitRpcResult")
+          var bAuthCompleted = false;
+
+          // See if we just completed an authentication
+          if (fsm.getPreviousState() == "State_Authenticate" &&
+              event.getType() == "complete")
+          {
+            bAuthCompleted = true;
+          }
+_this.debug("bAuthCompleted=" + bAuthCompleted);
+
+          // If we didn't just complete an authentication and we're coming
+          // from some other state...
+          if (! bAuthCompleted &&
+              fsm.getPreviousState() != "State_AwaitRpcResult")
           {
             // ... then push the previous state onto the state stack
+_this.warn("PUSHING STATE");
             fsm.pushState(false);
           }
         },
@@ -144,27 +157,63 @@ qx.Proto.addAwaitRpcResultState = function(module)
    * Cause: "failed" (on RPC) where reason is PermissionDenied
    */
   var trans = new qx.util.fsm.Transition(
-    "Transition_AwaitRpcResult_to_GetAuthInfo",
+    "Transition_AwaitRpcResult_to_Authenticate",
     {
       "nextState" :
-        qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK,
+        "State_Authenticate",
 
       "predicate" :
         function(fsm, event)
         {
           var error = event.getData(); // retrieve the JSON-RPC error
 
-          // Did we get get origin=Server, code=PermissionDenied ?
+          // Did we get get origin=Server, and either
+          // code=NotLoggedIn or code=SessionExpired ? 
           var origins = swat.main.AbstractModuleFsm.JsonRpc_Origin;
           var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
           if (error.origin == origins.Server &&
-              error.code == serverErrors.PermissionDenied)
+              (error.code == serverErrors.NotLoggedIn ||
+               error.code == serverErrors.SessionExpired))
           {
             return true;
           }
 
           // fall through to next transition, also for "failed"
           return false;
+        },
+
+      "ontransition" :
+        function(fsm, event)
+        {
+          var caption;
+
+          var error = event.getData(); // retrieve the JSON-RPC error
+          var serverErrors = swat.main.AbstractModuleFsm.JsonRpc_ServerError;
+
+          switch(error.code)
+          {
+          case serverErrors.NotLoggedIn:
+            caption = "Please log in.";
+            break;
+
+          case serverErrors.SessionExpired:
+          default:
+            caption = "Session Expired.  Please log in.";
+            break;
+          }
+
+          // Retrieve the modal authentication window.
+
+          var loginWin = swat.main.Authenticate.getInstance(module);
+
+          // Set the caption
+          loginWin.setCaption(caption);
+
+          // Set the domain info
+          loginWin.setInfo(error.info);
+
+          // Open the authentication window
+          loginWin.open();
         }
     });
   state.addTransition(trans);
@@ -247,6 +296,153 @@ qx.Proto.addAwaitRpcResultState = function(module)
         }
     });
   state.addTransition(trans);
+
+  /*
+   * State: Authenticate
+   *
+   * Transition on:
+   *  "execute" on login_button
+   */
+  var state = new qx.util.fsm.State(
+    "State_Authenticate",
+    {
+      "onentry" :
+        function(fsm, event)
+        {
+          // Retrieve the login window object
+          var win = module.fsm.getObject("login_window");
+
+          // Clear the password field
+          win.password.setValue("");
+
+          // If there's no value selected for domain...
+          if (win.domain.getValue() == null)
+          {
+            // ... then select the first value
+            win.domain.setSelected(win.domain.getList().getFirstChild());
+          }
+
+          // Retrieve the current RPC request
+          var rpcRequest = _this.getCurrentRpcRequest();
+
+          // Did we just return from an RPC request and was it a login request?
+          if (fsm.getPreviousState() == "State_AwaitRpcResult" &&
+              rpcRequest.service == "samba.system" &&
+              rpcRequest.params.length > 1 &&
+              rpcRequest.params[1] == "login")
+          {
+            // Yup.  Display the result.  Pop the old request off the stack
+            var loginRequest = _this.popRpcRequest();
+
+            // Retrieve the result
+            var result = loginRequest.getUserData("result");
+
+            // Did we succeed?
+            if (result.type == "failed")
+            {
+              // Nope.  Just reset the caption, and remain in this state.
+              win.setCaption("Login Failed.  Try again.");
+            }
+            else
+            {
+              // Login was successful.  Generate an event that will transition
+              // us back to the AwaitRpcResult state to again await the result
+              // of the original RPC request.
+              win.dispatchEvent(new qx.event.type.Event("complete"), true);
+
+              // Reissue the original request.  (We already popped the login
+              // request off the stack, so the current request is the original
+              // one.)
+              var origRequest = _this.getCurrentRpcRequest();
+              
+              // Retrieve the RPC object */
+              var rpc = fsm.getObject("swat.main.rpc");
+
+              // Set the service name
+              rpc.setServiceName(origRequest.service);
+
+              // Reissue the request
+              origRequest.request =
+                qx.io.remote.Rpc.prototype.callAsyncListeners.apply(
+                  rpc,
+                  origRequest.params);
+
+              // Clear the password field, for good measure
+              win.password.setValue("");
+
+              // Close the login window
+              win.close();
+            }
+
+            // Dispose of the login request
+            loginRequest.request.dispose();
+            loginRequest.request = null;
+          }
+        },
+
+      "events" :
+      {
+        "execute"  :
+        {
+          "login_button" :
+            "Transition_Authenticate_to_AwaitRpcResult_via_button_login"
+        },
+
+        "complete"  :
+        {
+          "login_window" :
+            "Transition_Authenticate_to_AwaitRpcResult_via_complete"
+        }
+      }
+    });
+  fsm.addState(state);
+
+  /*
+   * Transition: Authenticate to AwaitRpcResult
+   *
+   * Cause: "execute" on login_button
+   */
+  var trans = new qx.util.fsm.Transition(
+    "Transition_Authenticate_to_AwaitRpcResult_via_button_login",
+    {
+      "nextState" :
+        "State_AwaitRpcResult",
+
+      "ontransition" :
+        function(fsm, event)
+        {
+          // Retrieve the login window object
+          var win = fsm.getObject("login_window");
+
+          // Issue a Login call
+          _this.callRpc(fsm,
+                        "samba.system",
+                        "login",
+                        [
+                          win.userName.getValue(),
+                          win.password.getValue(),
+                          win.domain.getValue()
+                        ]);
+        }
+    });
+  state.addTransition(trans);
+
+  /*
+   * Transition: Authenticate to AwaitRpcResult
+   *
+   * Cause: "complete" on login_window
+   *
+   * We've already re-issued the original request, so we have nothing to do
+   * here but transition back to the AwaitRpcResult state to again await the
+   * result of the original request.
+   */
+  var trans = new qx.util.fsm.Transition(
+    "Transition_Authenticate_to_AwaitRpcResult_via_complete",
+    {
+      "nextState" :
+        "State_AwaitRpcResult"
+    });
+  state.addTransition(trans);
 };
 
 
@@ -292,7 +488,7 @@ qx.Proto.callRpc = function(fsm, service, method, params)
   // Set the service name
   rpc.setServiceName(rpcRequest.service);
 
-  // Issue the request, skipping the already-specified service name
+  // Issue the request
   rpcRequest.request =
     qx.io.remote.Rpc.prototype.callAsyncListeners.apply(rpc,
                                                         rpcRequest.params);
@@ -435,21 +631,46 @@ qx.Class.JsonRpc_ServerError =
    */
   PermissionDenied      : 6,
 
+  /*** Errors generated by this server which are not qooxdoo-standard ***/
+
   /*
-   * Error code, value 7: Unexpected Output
+   * Error code, value 1000: Unexpected Output
    *
    * The called method illegally generated output to the browser, which would
    * have preceeded the JSON-RPC data.
    */
-  UnexpectedOutput      : 7,
+  UnexpectedOutput      : 1000,
 
   /*
-   * Error code, value 8: Resource Error
+   * Error code, value 1001: Resource Error
    *
-   * Too many resources were requested, a system limitation on the total
-   * number of resources has been reached, or a resource or resource id was
-   * misused.
+   * Too many resources were requested, a system limitation on the total number
+   * of resources has been reached, or a resource or resource id was misused.
    */
-  ResourceError         : 8
+  ResourceError         : 1001,
 
+  /*
+   * Error code, value 1002: Not Logged In
+   *
+   * The user has logged out and must re-authenticate, or this is a brand new
+   * session and the user must log in.
+   *
+   */
+  NotLoggedIn           : 1002,
+
+  /*
+   * Error code, value 1003: Session Expired
+   *
+   * The session has expired and the user must re-authenticate.
+   *
+   */
+  SessionExpired        : 1003,
+
+  /*
+   * Error code, value 1004: Login Failed
+   *
+   * An attempt to log in failed.
+   *
+   */
+  LoginFailed           : 1004
 };
diff --git a/webapps/swat/source/class/swat/main/Authenticate.js b/webapps/swat/source/class/swat/main/Authenticate.js
new file mode 100644 (file)
index 0000000..449a17d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright:
+ *   (C) 2007 by Derrell Lipman
+ *       All rights reserved
+ *
+ * License:
+ *   LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
+ */
+
+/**
+ * Swat authentication window class
+ */
+qx.OO.defineClass("swat.main.Authenticate", qx.ui.window.Window,
+function(module)
+{
+  var o;
+  var fsm = module.fsm;
+
+  qx.ui.window.Window.call(this);
+
+  var addCaptionedWidget = function(caption, dest, addWidget)
+  {
+    // Add a row to the destination grid
+    dest.addRow();
+    var row = dest.getRowCount() - 1;
+    dest.setRowHeight(row, 24);
+
+    // Add the caption
+    var o = new qx.ui.basic.Label(caption);
+    dest.add(o, 0, row);
+
+    // Add the widget
+    o = addWidget();
+    o.setHeight(24);
+    dest.add(o, 1, row);
+
+    // Give 'em the varying data label
+    return o;
+  };
+
+
+  // Set characteristics of this window
+  this.set({
+             width         : 380,
+             height        : 200,
+             modal         : true,
+             centered      : true,
+             showClose     : false,
+             showMaximize  : false,
+             showMinimize  : false,
+             showStatusbar : false,
+             allowClose    : false,
+             allowMaximize : false,
+             allowMinimize : false,
+             resizeable    : false,
+             moveable      : false,
+             zIndex        : 10000
+           });
+
+
+  // Create a grid layout
+  var grid = new qx.ui.layout.GridLayout();
+  grid.setLocation(14, 14);
+  grid.setDimension("90%", "90%");
+  grid.setVerticalSpacing(14);
+  grid.setPadding(14, 14);
+  grid.setRowCount(0);
+  grid.setColumnCount(2);
+  grid.setColumnWidth(0, 100);
+  grid.setColumnWidth(1, 200);
+
+
+  // Add an input box for the user name
+  this.userName = addCaptionedWidget("User Name", grid,
+                                     function()
+                                     {
+                                       return new qx.ui.form.TextField();
+                                     });
+
+  // Add an input box for the password
+  this.password = addCaptionedWidget("Password", grid,
+                                     function()
+                                     {
+                                       return new qx.ui.form.PasswordField();
+                                     });
+
+  // Add an input box for the password
+  this.domain = addCaptionedWidget("Domain", grid,
+                                   function()
+                                   {
+                                     // Create a combo box for for the domain
+                                     var combo = new qx.ui.form.ComboBox();
+                                     combo.setEditable(false);
+                                     return combo;
+                                   });
+
+  // Add a login button
+  this.login = addCaptionedWidget("", grid,
+                                  function()
+                                  {
+                                    return new qx.ui.form.Button("Login");
+                                  });
+
+  // Save this login button since we receive events on it
+  fsm.addObject("login_button", this.login);
+
+  // We want to receive "execute" events on this button
+  this.login.addEventListener("execute", fsm.eventListener, fsm);
+
+  // Add the grid to the window
+  this.add(grid);
+
+  // Add this window to the document
+  this.addToDocument();
+
+  // Save this window object
+  fsm.addObject("login_window", this);
+
+  // We want to receive "complete" events on this button (which we generate)
+  this.addEventListener("complete", fsm.eventListener, fsm);
+});
+
+
+
+qx.Proto.setInfo = function(info)
+{
+  this.debug(info);
+
+  // Remove everythingn from the domain list
+  this.domain.removeAll();
+
+  // Add the available domains
+  for (var i = 0; i < info.length; i++)
+  {
+    var item = new qx.ui.form.ListItem(info[i]);
+    this.domain.add(item);
+  }
+};
+
+
+/**
+ * Singleton Instance Getter
+ */
+qx.Class.getInstance = function(module)
+{
+  if (! this._instance)
+  {
+    this._instance = new this(module);
+  }
+
+  return this._instance;
+};
index 7cc6b0173633f986268aa910c7484c9d78dada37..fda6ba111554d48bd2da24807e8e4f97744914fa 100644 (file)
@@ -10,6 +10,7 @@
 /*
 #require(swat.main.Module)
 #require(swat.main.AbstractModule)
+#require(swat.main.Authenticate);
 */
 
 /**
@@ -22,21 +23,22 @@ function()
 });
 
 /*
- * Register our supported modules
+ * Register our supported modules.  The order listed here is the order they
+ * will appear in the Modules menu.
  */
 
 //#require(swat.module.statistics.Statistics)
 new swat.main.Module("Status and Statistics",
-                       swat.module.statistics.Statistics);
+                     swat.module.statistics.Statistics);
 
 //#require(swat.module.ldbbrowse.LdbBrowse)
 new swat.main.Module("LDB Browser",
-                       swat.module.ldbbrowse.LdbBrowse);
+                     swat.module.ldbbrowse.LdbBrowse);
 
 //#require(swat.module.documentation.Documentation)
 //#require(api.Viewer)
 new swat.main.Module("API Documentation",
-                       swat.module.documentation.Documentation);
+                     swat.module.documentation.Documentation);
 
 
 /*
index 0abb3454c8b2a1a160287c8422b526cf35b0d769..4ddc01859546ca376800959ec496458221e03a96 100644 (file)
@@ -37,7 +37,7 @@ qx.Proto.buildFsm = function(module)
     "State_Idle",
     {
       "onentry" :
-        function(fsm, state)
+        function(fsm, event)
         {
           // Did we just return from an RPC request?
           if (fsm.getPreviousState() == "State_AwaitRpcResult")
index 3083fed42af05b1f3956651cf62250d266acd328..b60501512a8aa12e063fe2d65eafef69feec43ea 100644 (file)
@@ -61,7 +61,7 @@ qx.Proto.buildFsm = function(module)
     "State_Idle",
     {
       "onentry" :
-        function(fsm, state)
+        function(fsm, event)
         {
           // Did we just return from an RPC request?
           if (fsm.getPreviousState() == "State_AwaitRpcResult")
@@ -83,7 +83,7 @@ qx.Proto.buildFsm = function(module)
         },
 
       "onexit" :
-        function(fsm, state)
+        function(fsm, event)
         {
           // If we're not coming right back into this state...
           if (fsm.getNextState() != "State_Idle")
index 5968785e079a17d861fdbb6a61a4739d5b140918..b5e11d453323c9a9a2980cee5f2e05e8bdf9dab7 100644 (file)
@@ -82,7 +82,7 @@ qx.Proto.buildGui = function(module)
     dest.setRowHeight(row, 16);
 
     // Add the caption
-    o = new qx.ui.basic.Label(caption);
+    var o = new qx.ui.basic.Label(caption);
     dest.add(o, 0, row);
 
     // Add the text field that will contain varying data