61a61ed4472c27f9f6e9932bfb53871cf6291fa7
[gssweb.git] / navigator.gssweb.js
1 /*
2  * Copyright (c) 2015, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 console.log("Loading navigator.gssweb.js - #4");
35
36
37 var GSSWeb = (function () {
38   function GSSWeb(config) {
39     this.serverPath = config.serverPath;
40     this.credential = config.credential;
41     this.appTag = config.appTag || "GSSWeb-" + navigator.generateNonce();
42     this.error = config.error || function (err) {
43       console.warn(err);
44     };
45     this.success = config.success;
46
47     this.version = "0.0.1";
48     this.authenticationState = false;
49     this.context = "";
50     this.serverToken = "";
51     this.clientCred = "";
52     this.xhr = new XMLHttpRequest();
53
54     this.gss = new navigator.gssEap({
55       appTag: this.appTag,
56       error: this.handleGSSError.bind(this)
57     });
58   }
59   /* What to invoke when the underlying GSS library 
60    * has a problem.
61    */
62   GSSWeb.prototype.handleGSSError = 
63     function (major, minor, errMsg, appTag) {
64       this.error(errMsg, appTag);
65     };
66
67   /* The basic authenticate function.
68    * Takes no arguments, as all should be supplied
69    * on the base object.
70    */
71   GSSWeb.prototype.authenticate = function () {
72     /* Error checking */
73     // Ensure that the callbacks exist well
74     if ( "function" != typeof(this.error) )
75     {
76       // I can't even call my error function!  All that can be done
77       // is throw an error.
78       throw("Error function not supplied to navigator.gssweb object!");
79     }
80     if ( "function" != typeof(this.success) )
81     {
82       // OK, so we have an error function; use it.
83       this.error(
84         "Success function not supplied to navigator.gssweb object!"
85       );
86     }
87     if ( ! this.presentString(this.serverPath) )
88     {
89       this.error("Server path not supplied to navigator.gssweb object!");
90     }
91
92     
93     /* Setup */
94     this.nonce = navigator.generateNonce();
95
96     // Start off the cascade by getting the
97     // GSS name of the server
98     this.authGetServerName();
99   };
100
101   GSSWeb.prototype.authGetServerName = function () {
102     this.gss.import_name({
103       name: "HTTP@" + window.location.hostname,
104       success: this.authReceiveServerName.bind(this)
105     });
106   };
107   GSSWeb.prototype.authReceiveServerName = function (data, appTag) {
108     this.serverName = data.gss_name;
109
110     /* Either move on to acquire_cred because we have been
111     * supplied a credential, or move on to init_sec_context
112     * when we have not.
113     */
114     if ( this.presentString(this.credential) )
115       this.authGetClientName();
116     else
117       this.authInitSecContext();
118   };
119
120   GSSWeb.prototype.authGetClientName = function () {
121     this.gss.import_name({
122       name: this.credential,
123       success: this.authReceiveClientName.bind(this)
124     });
125   };
126   GSSWeb.prototype.authReceiveClientName = function (data, appTag) {
127     this.clientName = data.gss_name;
128
129     // Next up: Get the local credential
130     this.authAcquireCred();
131   };
132
133   GSSWeb.prototype.authAcquireCred = function () {
134     this.gss.acquire_cred({
135       desired_name: this.clientName,
136       cred_usage: 1,
137       success: this.authReceiveClientCred.bind(this)
138     });
139   };
140   GSSWeb.prototype.authReceiveClientCred =
141     function (cred, actual_mechs, lifetime_rec) {
142       this.clientCred = cred;
143
144       this.authInitSecContext();
145     };
146
147   GSSWeb.prototype.authInitSecContext = function () {
148     var params = {
149       target_name: this.serverName,
150       success: this.sendTokenToServer.bind(this)
151     };
152
153     if ("" != this.clientCred) {
154       params.cred_handle = this.clientCred;
155     }
156     if ("" != this.serverToken) {
157       params.input_token = this.serverToken;
158     }
159     if ("" != this.context) {
160       params.context_handle = this.context;
161     }
162
163     this.gss.init_sec_context(params);
164   };
165
166   GSSWeb.prototype.sendTokenToServer = 
167     function (data, 
168               app_tag) {
169     this.clientToken = data.output_token;
170     this.context = data.context_handle;
171
172     var msg = "nonce=" + this.nonce +
173                "&token=" + encodeURIComponent(this.clientToken);
174
175     this.xhr.open("POST", this.serverPath, true);
176
177     this.xhr.setRequestHeader(
178       'Content-Type', 
179       'application/x-www-form-urlencoded'
180     );
181     this.xhr.onreadystatechange = this.recvTokenFromServer.bind(this);
182
183     this.xhr.send(msg);
184   };
185
186   GSSWeb.prototype.recvTokenFromServer = function () {
187     // Only care when we're ready
188     if (this.xhr.readyState != 4) {
189       return;
190     }
191
192     switch (this.xhr.status) {
193       case 200:
194         // Finished!
195         var serverResponse = JSON.parse(this.xhr.responseText);
196         var decoded = window.atob(serverResponse.application.data);
197         this.authenticationState = true;
198         this.success(
199           decoded,
200           serverResponse.application["content-type"],
201           this.appTag
202         );
203         break;
204       case 401:
205         // Continue needed
206         var serverResponse = JSON.parse(this.xhr.responseText);
207         this.serverToken = serverResponse.gssweb.token;
208         this.authInitSecContext();
209         break;
210       default:
211         // We have some server-reported error
212         this.error(
213           window.location.hostname + 
214           " reported an error; aborting",
215           this.appTag
216         );
217
218         // Destroy the GSS context.
219         this.context = undefined;
220         return;
221     }
222   };
223   
224   /*************************************
225    * Utility methods
226    *************************************/
227   // return true if the variable is a non-empty string
228   GSSWeb.prototype.presentString = function(str)
229   {
230     return(
231       "string" == typeof(str) &&
232       "" != str
233     );
234   }
235   
236   return GSSWeb;
237 })();
238
239 navigator.gssweb = GSSWeb;