Skip to content

Commit

Permalink
Replace auth.js script with config expression
Browse files Browse the repository at this point in the history
  • Loading branch information
zaidka committed Mar 15, 2019
1 parent 6c6d88e commit 2c9e7e8
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 235 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
node_modules
dist
debug
config/auth.js
config/config.json
config/config-ui.json
config/cwmp.key
Expand Down
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
*~
.npmignore

config/auth.js
config/config.json
config/cwmp.key
config/cwmp.crt
Expand Down
1 change: 0 additions & 1 deletion build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ async function copyStatic(): Promise<void> {
"CHANGELOG.md",
"npm-shrinkwrap.json",
"config/config-sample.json",
"config/auth-sample.js",
"config/ext-sample.js",
"public/logo.svg",
"public/favicon.png"
Expand Down
7 changes: 0 additions & 7 deletions config/auth-sample.js

This file was deleted.

290 changes: 74 additions & 216 deletions lib/api-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,188 +17,24 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/

import * as crypto from "crypto";
import * as dgram from "dgram";
import * as URL from "url";
import * as http from "http";
import * as config from "./config";
import * as db from "./db";
import * as common from "./common";
import * as auth from "./auth";
import * as cache from "./cache";

function udpConReq(address, un, key, callback): boolean {
if (!address) return false;

let [host, port] = address.split(":", 2);

if (!port) {
port = 80;
} else {
port = parseInt(port);
if (isNaN(port)) return false;
}

const ts = Math.trunc(Date.now() / 1000);
const id = Math.trunc(Math.random() * 4294967295);
const cn = crypto.randomBytes(8).toString("hex");
const sig = crypto
.createHmac("sha1", key)
.update(`${ts}${id}${un}${cn}`)
.digest("hex");
const uri = `http://${address}?ts=${ts}&id=${id}&un=${un}&cn=${cn}&sig=${sig}`;
const message = Buffer.from(
`GET ${uri} HTTP/1.1\r\nHost: ${address}\r\n\r\n`
);

const client = dgram.createSocket({ type: "udp4", reuseAddr: true });
const UDP_CONNECTION_REQUEST_PORT = +config.get(
"UDP_CONNECTION_REQUEST_PORT"
);

if (UDP_CONNECTION_REQUEST_PORT)
// When a device is NAT'ed, the UDP Connection Request must originate from
// the same address and port used by the STUN server, in order to traverse
// the firewall. This does require that the Genieacs NBI and STUN server
// are allowed to bind to the same address and port. The STUN server needs
// to open its UDP port with the SO_REUSEADDR option, allowing the NBI to
// also bind to the same port.
client.bind({ port: UDP_CONNECTION_REQUEST_PORT, exclusive: true });

let count = 3;
const func = (err: Error): void => {
if (err || --count <= 0) {
client.close();
return void callback(err);
}
client.send(message, 0, message.length, port, host, func);
};
client.send(message, 0, message.length, port, host, func);
return true;
}

function httpConReq(
url,
username,
password,
allowBasicAuth,
timeout,
callback
): void {
const options: http.RequestOptions = URL.parse(url);
if (options.protocol !== "http:") {
return void callback(
new Error("Invalid connection request URL or protocol")
);
}
options.agent = new http.Agent({ maxSockets: 1 });

function statusToError(statusCode): Error {
switch (statusCode) {
case 200:
case 204:
return null;
case 401:
return new Error("Incorrect connection request credentials");
case 0:
return new Error("Device is offline");
default:
return new Error(`Unexpected response code from device: ${statusCode}`);
}
}

const request = http
.get(options, response => {
if (response.statusCode === 401 && response.headers["www-authenticate"]) {
const authHeader = auth.parseAuthHeader(
response.headers["www-authenticate"]
);
if (authHeader["method"] === "Basic") {
if (!allowBasicAuth) {
request.abort();
return void callback(
new Error("Basic HTTP authentication not allowed")
);
}
options.headers = {
Authorization: auth.basic(username || "", password || "")
};
} else if (authHeader["method"] === "Digest") {
options.headers = {
Authorization: auth.digest(
username || "",
password || "",
options.path,
"GET",
null,
authHeader
)
};
}

const req = http
.get(options, res => {
if (res.statusCode === 0) {
// Workaround for some devices unexpectedly closing the connection
const req2 = http
.get(options, res2 => {
callback(statusToError(res2.statusCode));
res2.resume();
})
.on("error", () => {
req2.abort();
callback(statusToError(0));
})
.on("socket", socket => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
req2.abort();
});
});
} else {
callback(statusToError(res.statusCode));
}
res.resume();
})
.on("error", () => {
req.abort();
callback(statusToError(0));
})
.on("socket", socket => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
req.abort();
});
});
} else {
callback(statusToError(response.statusCode));
}
// No listener for data so emit resume
response.resume();
})
.on("error", () => {
request.abort();
callback(statusToError(0));
})
.on("socket", socket => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
request.abort();
});
});
}
import {
getCurrentSnapshot,
getConfig,
getConfigExpression
} from "./local-cache";
import {
httpConnectionRequest,
udpConnectionRequest
} from "./connection-request";
import { Expression } from "./types";

export function connectionRequest(deviceId, callback): void {
const CONNECTION_REQUEST_TIMEOUT = config.get(
"CONNECTION_REQUEST_TIMEOUT",
deviceId
);
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = config.get(
"CONNECTION_REQUEST_ALLOW_BASIC_AUTH",
deviceId
);
const options = {
projection: {
_deviceId: 1,
"Device.ManagementServer.ConnectionRequestURL._value": 1,
"Device.ManagementServer.UDPConnectionRequestAddress._value": 1,
"Device.ManagementServer.ConnectionRequestUsername._value": 1,
Expand Down Expand Up @@ -236,52 +72,74 @@ export function connectionRequest(deviceId, callback): void {
if (managementServer.ConnectionRequestPassword)
password = managementServer.ConnectionRequestPassword._value;

const conReq = (): void => {
const udpSent = udpConReq(
udpConnectionRequestAddress,
username,
password,
err => {
if (err) throw err;
const context = {
id: device["_id"],
serialNumber: device["_deviceId"]["SerialNumber"],
productClass: device["_deviceId"]["ProductClass"],
oui: device["_deviceId"]["OUI"],
username: username || "",
password: password || ""
};

getCurrentSnapshot()
.then(snapshot => {
const now = Date.now();
const UDP_CONNECTION_REQUEST_PORT = +getConfig(
snapshot,
"cwmp.udpConnectionRequestAuth",
context,
now
);
const CONNECTION_REQUEST_TIMEOUT = +getConfig(
snapshot,
"cwmp.connectionRequestTimeout",
context,
now
);
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = !!getConfig(
snapshot,
"cwmp.connectionRequestAllowBasicAuth",
context,
now
);
let authExp: Expression = getConfigExpression(
snapshot,
"cwmp.connectionRequestAuth"
);

if (authExp === undefined) {
authExp = [
"FUNC",
"AUTH",
["PARAM", "username"],
["PARAM", "password"]
] as Expression;
}
);

httpConReq(
connectionRequestUrl,
username,
password,
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT,
err => {
if (udpSent) return void callback();
callback(err);
let udpProm;
if (udpConnectionRequestAddress) {
udpProm = udpConnectionRequest(
udpConnectionRequestAddress,
authExp,
db.configCollection,
UDP_CONNECTION_REQUEST_PORT
);
}
);
};

if (config.auth && config.auth.connectionRequest) {
// Callback is optional for backward compatibility
if (config.auth.connectionRequest.length > 4) {
return void config.auth.connectionRequest(
deviceId,
httpConnectionRequest(
connectionRequestUrl,
username,
password,
(u, p) => {
username = u;
password = p;
conReq();
}
);
}
[username, password] = config.auth.connectionRequest(
deviceId,
connectionRequestUrl,
username,
password
);
}
conReq();
authExp,
context,
CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT
)
.then(callback)
.catch(err => {
if (udpProm) return void udpProm.then(callback).catch(callback);
callback(err);
});
})
.catch(callback);
});
}

Expand Down
8 changes: 0 additions & 8 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,3 @@ export function getDefault(optionName): string | number | boolean {

return val;
}

export let auth;
// Load authentication scripts
try {
auth = require(resolve(allConfig.CONFIG_DIR as string, "auth.js"));
} catch (error) {
// No auth.js exists
}
Loading

0 comments on commit 2c9e7e8

Please sign in to comment.