Final fixes for getting GSSWeb to work again.
[gssweb.git] / navigator.gssweb.js
1 /* 
2  * The MIT License (MIT)
3  * 
4  * Copyright (c) 2015 JISC
5  * 
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  * 
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  * 
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24
25
26 var GSSWeb = (function () {
27   function GSSWeb(config) {
28     this.serverPath = config.serverPath;
29     this.credential = config.credential;
30     this.appTag = config.appTag || "GSSWeb-" + navigator.generateNonce();
31     this.error = config.error || function (err) {
32       console.warn(err);
33     };
34     this.success = config.success;
35
36     this.version = "0.0.1";
37     this.authenticationState = false;
38     this.context = "";
39     this.serverToken = "";
40     this.clientCred = "";
41     this.xhr = new XMLHttpRequest();
42
43     this.gss = new navigator.gss({
44       appTag: this.appTag,
45       error: this.handleGSSError.bind(this)
46     });
47   }
48   /* What to invoke when the underlying GSS library 
49    * has a problem.
50    */
51   GSSWeb.prototype.handleGSSError = 
52     function (major, minor, errMsg, appTag) {
53       this.error(errMsg, appTag);
54     };
55
56   /* The basic authenticate function.
57    * Takes no arguments, as all should be supplied
58    * on the base object.
59    */
60   GSSWeb.prototype.authenticate = function () {
61     /* Error checking */
62     // Ensure that the callbacks exist well
63     if ( "function" != typeof(this.error) )
64     {
65       // I can't even call my error function!  All that can be done
66       // is throw an error.
67       throw("Error function not supplied to navigator.gssweb object!");
68     }
69     if ( "function" != typeof(this.success) )
70     {
71       // OK, so we have an error function; use it.
72       this.error(
73         "Success function not supplied to navigator.gssweb object!"
74       );
75     }
76     if ( ! this.presentString(this.serverPath) )
77     {
78       this.error("Server path not supplied to navigator.gssweb object!");
79     }
80
81     
82     /* Setup */
83     this.nonce = navigator.generateNonce();
84
85     // Start off the cascade by getting the
86     // GSS name of the server
87     this.authGetServerName();
88   };
89
90   GSSWeb.prototype.authGetServerName = function () {
91     this.gss.import_name({
92       name: "HTTP@" + window.location.hostname,
93       success: this.authReceiveServerName.bind(this)
94     });
95   };
96   GSSWeb.prototype.authReceiveServerName = function (data, appTag) {
97     this.serverName = data.gss_name;
98
99     /* Either move on to acquire_cred because we have been
100     * supplied a credential, or move on to init_sec_context
101     * when we have not.
102     */
103     if ( this.presentString(this.credential) )
104       this.authGetClientName();
105     else
106       this.authInitSecContext();
107   };
108
109   GSSWeb.prototype.authGetClientName = function () {
110     this.gss.import_name({
111       name: this.credential,
112       success: this.authReceiveClientName.bind(this)
113     });
114   };
115   GSSWeb.prototype.authReceiveClientName = function (data, appTag) {
116     this.clientName = data.gss_name;
117
118     // Next up: Get the local credential
119     this.authAcquireCred();
120   };
121
122   GSSWeb.prototype.authAcquireCred = function () {
123     this.gss.acquire_cred({
124       desired_name: this.clientName,
125       cred_usage: 1,
126       success: this.authReceiveClientCred.bind(this)
127     });
128   };
129   GSSWeb.prototype.authReceiveClientCred =
130     function (cred, actual_mechs, lifetime_rec) {
131       this.clientCred = cred;
132
133       this.authInitSecContext();
134     };
135
136   GSSWeb.prototype.authInitSecContext = function () {
137     var params = {
138       target_name: this.serverName,
139       success: this.sendTokenToServer.bind(this)
140     };
141
142     if ("" != this.clientCred) {
143       params.cred_handle = this.clientCred;
144     }
145     if ("" != this.serverToken) {
146       params.input_token = this.serverToken;
147     }
148     if ("" != this.context) {
149       params.context_handle = this.context;
150     }
151
152     this.gss.init_sec_context(params);
153   };
154
155   GSSWeb.prototype.sendTokenToServer = 
156     function (data, 
157               app_tag) {
158     this.clientToken = data.output_token;
159     this.context = data.context_handle;
160
161     var msg = "nonce=" + this.nonce +
162                "&token=" + encodeURIComponent(this.clientToken);
163
164     this.xhr.open("POST", this.serverPath, true);
165
166     this.xhr.setRequestHeader(
167       'Content-Type', 
168       'application/x-www-form-urlencoded'
169     );
170     this.xhr.onreadystatechange = this.recvTokenFromServer.bind(this);
171
172     this.xhr.send(msg);
173   };
174
175   GSSWeb.prototype.recvTokenFromServer = function () {
176     // Only care when we're ready
177     if (this.xhr.readyState != 4) {
178       return;
179     }
180
181     switch (this.xhr.status) {
182       case 200:
183         // Finished!
184         var serverResponse = JSON.parse(this.xhr.responseText);
185         var decoded = window.atob(serverResponse.application.data);
186         this.authenticationState = true;
187         this.success(
188           decoded,
189           serverResponse.application["content-type"],
190           this.appTag
191         );
192         break;
193       case 401:
194         // Continue needed
195         var serverResponse = JSON.parse(this.xhr.responseText);
196         this.serverToken = serverResponse.gssweb.token;
197         this.authInitSecContext();
198         break;
199       default:
200         // We have some server-reported error
201         this.error(
202           window.location.hostname + 
203           " reported an error; aborting",
204           this.appTag
205         );
206
207         // Destroy the GSS context.
208         this.context = undefined;
209         return;
210     }
211   };
212   
213   /*************************************
214    * Utility methods
215    *************************************/
216   // return true if the variable is a non-empty string
217   GSSWeb.prototype.presentString = function(str)
218   {
219     return(
220       "string" == typeof(str) &&
221       "" != str
222     );
223   }
224   
225   return GSSWeb;
226 })();
227
228 navigator.gssweb = GSSWeb;