summaryrefslogtreecommitdiffstats
path: root/devtools/shared/security/auth.js
blob: 57e3ebe6fff985c08d3086e83afc2850cc252e99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

loader.lazyRequireGetter(
  this,
  "prompt",
  "resource://devtools/shared/security/prompt.js"
);

/**
 * A simple enum-like object with keys mirrored to values.
 * This makes comparison to a specfic value simpler without having to repeat and
 * mis-type the value.
 */
function createEnum(obj) {
  for (const key in obj) {
    obj[key] = key;
  }
  return obj;
}

/**
 * |allowConnection| implementations can return various values as their |result|
 * field to indicate what action to take.  By specifying these, we can
 * centralize the common actions available, while still allowing embedders to
 * present their UI in whatever way they choose.
 */
var AuthenticationResult = (exports.AuthenticationResult = createEnum({
  /**
   * Close all listening sockets, and disable them from opening again.
   */
  DISABLE_ALL: null,

  /**
   * Deny the current connection.
   */
  DENY: null,

  /**
   * Additional data needs to be exchanged before a result can be determined.
   */
  PENDING: null,

  /**
   * Allow the current connection.
   */
  ALLOW: null,

  /**
   * Allow the current connection, and persist this choice for future
   * connections from the same client.  This requires a trustable mechanism to
   * identify the client in the future.
   */
  ALLOW_PERSIST: null,
}));

/**
 * An |Authenticator| implements an authentication mechanism via various hooks
 * in the client and server debugger socket connection path (see socket.js).
 *
 * |Authenticator|s are stateless objects.  Each hook method is passed the state
 * it needs by the client / server code in socket.js.
 *
 * Separate instances of the |Authenticator| are created for each use (client
 * connection, server listener) in case some methods are customized by the
 * embedder for a given use case.
 */
var Authenticators = {};

/**
 * The Prompt authenticator displays a server-side user prompt that includes
 * connection details, and asks the user to verify the connection.  There are
 * no cryptographic properties at work here, so it is up to the user to be sure
 * that the client can be trusted.
 */
var Prompt = (Authenticators.Prompt = {});

Prompt.mode = "PROMPT";

Prompt.Client = function () {};
Prompt.Client.prototype = {
  mode: Prompt.mode,

  /**
   * When client is about to make a new connection, verify that the connection settings
   * are compatible with this authenticator.
   * @throws if validation requirements are not met
   */
  validateSettings() {},

  /**
   * When client has just made a new socket connection, validate the connection
   * to ensure it meets the authenticator's policies.
   *
   * @param host string
   *        The host name or IP address of the devtools server.
   * @param port number
   *        The port number of the devtools server.
   * @param encryption boolean (optional)
   *        Whether the server requires encryption.  Defaults to false.
   * @param s nsISocketTransport
   *        Underlying socket transport, in case more details are needed.
   * @return boolean
   *         Whether the connection is valid.
   */
  validateConnection() {
    return true;
  },

  /**
   * Work with the server to complete any additional steps required by this
   * authenticator's policies.
   *
   * Debugging commences after this hook completes successfully.
   *
   * @param host string
   *        The host name or IP address of the devtools server.
   * @param port number
   *        The port number of the devtools server.
   * @param encryption boolean (optional)
   *        Whether the server requires encryption.  Defaults to false.
   * @param transport DebuggerTransport
   *        A transport that can be used to communicate with the server.
   * @return A promise can be used if there is async behavior.
   */
  authenticate() {},
};

Prompt.Server = function () {};
Prompt.Server.prototype = {
  mode: Prompt.mode,

  /**
   * Augment the service discovery advertisement with any additional data needed
   * to support this authentication mode.
   *
   * @param listener SocketListener
   *        The socket listener that was just opened.
   * @param advertisement object
   *        The advertisement being built.
   */
  augmentAdvertisement(listener, advertisement) {
    advertisement.authentication = Prompt.mode;
  },

  /**
   * Determine whether a connection the server should be allowed or not based on
   * this authenticator's policies.
   *
   * @param session object
   *        In PROMPT mode, the |session| includes:
   *        {
   *          client: {
   *            host,
   *            port
   *          },
   *          server: {
   *            host,
   *            port
   *          },
   *          transport
   *        }
   * @return An AuthenticationResult value.
   *         A promise that will be resolved to the above is also allowed.
   */
  authenticate({ client, server }) {
    if (!Services.prefs.getBoolPref("devtools.debugger.prompt-connection")) {
      return AuthenticationResult.ALLOW;
    }
    return this.allowConnection({
      authentication: this.mode,
      client,
      server,
    });
  },

  /**
   * Prompt the user to accept or decline the incoming connection.  The default
   * implementation is used unless this is overridden on a particular
   * authenticator instance.
   *
   * It is expected that the implementation of |allowConnection| will show a
   * prompt to the user so that they can allow or deny the connection.
   *
   * @param session object
   *        In PROMPT mode, the |session| includes:
   *        {
   *          authentication: "PROMPT",
   *          client: {
   *            host,
   *            port
   *          },
   *          server: {
   *            host,
   *            port
   *          }
   *        }
   * @return An AuthenticationResult value.
   *         A promise that will be resolved to the above is also allowed.
   */
  allowConnection: prompt.Server.defaultAllowConnection,
};

exports.Authenticators = {
  get(mode) {
    if (!mode) {
      mode = Prompt.mode;
    }
    for (const key in Authenticators) {
      const auth = Authenticators[key];
      if (auth.mode === mode) {
        return auth;
      }
    }
    throw new Error("Unknown authenticator mode: " + mode);
  },
};