Bumping manifests a=b2g-bump
[gecko.git] / toolkit / identity / MinimalIdentity.jsm
blob723bf9cd3db9f181b3f88d9b51fbcc50268ba183
1 /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8  * This alternate implementation of IdentityService provides just the
9  * channels for navigator.id, leaving the certificate storage to a
10  * server-provided app.
11  *
12  * On b2g, the messages identity-controller-watch, -request, and
13  * -logout, are observed by the component SignInToWebsite.jsm.
14  */
16 "use strict";
18 this.EXPORTED_SYMBOLS = ["IdentityService"];
20 const Cu = Components.utils;
21 const Ci = Components.interfaces;
22 const Cc = Components.classes;
23 const Cr = Components.results;
25 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
26 Cu.import("resource://gre/modules/Services.jsm");
27 Cu.import("resource://gre/modules/identity/LogUtils.jsm");
29 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
30                                   "resource://gre/modules/identity/IdentityUtils.jsm");
32 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
33                                   "resource://gre/modules/identity/IdentityUtils.jsm");
35 function log(...aMessageArgs) {
36   Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs));
38 function reportError(...aMessageArgs) {
39   Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
42 function IDService() {
43   Services.obs.addObserver(this, "quit-application-granted", false);
45   // simplify, it's one object
46   this.RP = this;
47   this.IDP = this;
49   // keep track of flows
50   this._rpFlows = {};
51   this._authFlows = {};
52   this._provFlows = {};
55 IDService.prototype = {
56   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
58   observe: function observe(aSubject, aTopic, aData) {
59     switch (aTopic) {
60       case "quit-application-granted":
61         this.shutdown();
62         break;
63     }
64   },
66   shutdown: function() {
67     Services.obs.removeObserver(this, "quit-application-granted");
68   },
70   /**
71    * Parse an email into username and domain if it is valid, else return null
72    */
73   parseEmail: function parseEmail(email) {
74     var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/);
75     if (match) {
76       return {
77         username: match[1],
78         domain: match[2]
79       };
80     }
81     return null;
82   },
84   /**
85    * Register a listener for a given windowID as a result of a call to
86    * navigator.id.watch().
87    *
88    * @param aCaller
89    *        (Object)  an object that represents the caller document, and
90    *                  is expected to have properties:
91    *                  - id (unique, e.g. uuid)
92    *                  - loggedInUser (string or null)
93    *                  - origin (string)
94    *
95    *                  and a bunch of callbacks
96    *                  - doReady()
97    *                  - doLogin()
98    *                  - doLogout()
99    *                  - doError()
100    *                  - doCancel()
101    *
102    */
103   watch: function watch(aRpCaller) {
104     // store the caller structure and notify the UI observers
105     this._rpFlows[aRpCaller.id] = aRpCaller;
107     log("flows:", Object.keys(this._rpFlows).join(', '));
109     let options = makeMessageObject(aRpCaller);
110     log("sending identity-controller-watch:", options);
111     Services.obs.notifyObservers({wrappedJSObject: options},"identity-controller-watch", null);
112   },
114   /*
115    * The RP has gone away; remove handles to the hidden iframe.
116    * It's probable that the frame will already have been cleaned up.
117    */
118   unwatch: function unwatch(aRpId, aTargetMM) {
119     let rp = this._rpFlows[aRpId];
120     if (!rp) {
121       return;
122     }
124     let options = makeMessageObject({
125       id: aRpId,
126       origin: rp.origin,
127       messageManager: aTargetMM
128     });
129     log("sending identity-controller-unwatch for id", options.id, options.origin);
130     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null);
132     // Stop sending messages to this window
133     delete this._rpFlows[aRpId];
134   },
136   /**
137    * Initiate a login with user interaction as a result of a call to
138    * navigator.id.request().
139    *
140    * @param aRPId
141    *        (integer)  the id of the doc object obtained in .watch()
142    *
143    * @param aOptions
144    *        (Object)  options including privacyPolicy, termsOfService
145    */
146   request: function request(aRPId, aOptions) {
147     let rp = this._rpFlows[aRPId];
148     if (!rp) {
149       reportError("request() called before watch()");
150       return;
151     }
153     // Notify UX to display identity picker.
154     // Pass the doc id to UX so it can pass it back to us later.
155     let options = makeMessageObject(rp);
156     objectCopy(aOptions, options);
157     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-request", null);
158   },
160   /**
161    * Invoked when a user wishes to logout of a site (for instance, when clicking
162    * on an in-content logout button).
163    *
164    * @param aRpCallerId
165    *        (integer)  the id of the doc object obtained in .watch()
166    *
167    */
168   logout: function logout(aRpCallerId) {
169     let rp = this._rpFlows[aRpCallerId];
170     if (!rp) {
171       reportError("logout() called before watch()");
172       return;
173     }
175     let options = makeMessageObject(rp);
176     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null);
177   },
179   childProcessShutdown: function childProcessShutdown(messageManager) {
180     Object.keys(this._rpFlows).forEach(function(key) {
181       if (this._rpFlows[key]._mm === messageManager) {
182         log("child process shutdown for rp", key, "- deleting flow");
183         delete this._rpFlows[key];
184       }
185     }, this);
186   },
188   /*
189    * once the UI-and-display-logic components have received
190    * notifications, they call back with direct invocation of the
191    * following functions (doLogin, doLogout, or doReady)
192    */
194   doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) {
195     let rp = this._rpFlows[aRpCallerId];
196     if (!rp) {
197       log("WARNING: doLogin found no rp to go with callerId " + aRpCallerId);
198       return;
199     }
201     rp.doLogin(aAssertion, aInternalParams);
202   },
204   doLogout: function doLogout(aRpCallerId) {
205     let rp = this._rpFlows[aRpCallerId];
206     if (!rp) {
207       log("WARNING: doLogout found no rp to go with callerId " + aRpCallerId);
208       return;
209     }
211     // Logout from every site with the same origin
212     let origin = rp.origin;
213     Object.keys(this._rpFlows).forEach(function(key) {
214       let rp = this._rpFlows[key];
215       if (rp.origin === origin) {
216         rp.doLogout();
217       }
218     }.bind(this));
219   },
221   doReady: function doReady(aRpCallerId) {
222     let rp = this._rpFlows[aRpCallerId];
223     if (!rp) {
224       log("WARNING: doReady found no rp to go with callerId " + aRpCallerId);
225       return;
226     }
228     rp.doReady();
229   },
231   doCancel: function doCancel(aRpCallerId) {
232     let rp = this._rpFlows[aRpCallerId];
233     if (!rp) {
234       log("WARNING: doCancel found no rp to go with callerId " + aRpCallerId);
235       return;
236     }
238     rp.doCancel();
239   }
242 this.IdentityService = new IDService();