Add support for CPE authentication
This commit is contained in:
1 parent
53032c83f1
commit
ae3779c484
5 files changed
+123
-11
No files matched your search
+21
-1
@@ -19,7 +19,27 @@
|
||||
|
||||
import { createHash, randomBytes, pbkdf2 } from "crypto";
|
||||
|
||||
export function parseAuthHeader(authHeader): {} {
|
||||
export function parseAuthorizationHeader(authHeader): { method: string } {
|
||||
authHeader = authHeader.trim();
|
||||
const method = authHeader.split(" ", 1)[0];
|
||||
const res = { method: method };
|
||||
|
||||
if (method === "Basic") {
|
||||
// Inspired by https://github.com/jshttp/basic-auth
|
||||
const USER_PASS_REGEX = /^([^:]*):(.*)$/;
|
||||
const creds = USER_PASS_REGEX.exec(
|
||||
Buffer.from(authHeader.slice(method.length + 1), "base64").toString()
|
||||
);
|
||||
|
||||
if (!creds) throw new Error("Unable to parse auth header");
|
||||
res["username"] = creds[1];
|
||||
res["password"] = creds[2];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function parseWwwAuthenticateHeader(authHeader): {} {
|
||||
authHeader = authHeader.trim();
|
||||
const method = authHeader.split(" ", 1)[0];
|
||||
const res = { method: method };
|
||||
|
||||
@@ -134,7 +134,9 @@ export async function httpConnectionRequest(
|
||||
if (res.statusCode === 200 || res.statusCode === 204) return;
|
||||
|
||||
if (res.statusCode === 401 && res.headers["www-authenticate"]) {
|
||||
authHeader = auth.parseAuthHeader(res.headers["www-authenticate"]);
|
||||
authHeader = auth.parseWwwAuthenticateHeader(
|
||||
res.headers["www-authenticate"]
|
||||
);
|
||||
[username, password, authExp] = extractAuth(authExp, context, now, false);
|
||||
} else {
|
||||
throw new Error(
|
||||
|
||||
+96
-8
@@ -21,6 +21,7 @@ import * as zlib from "zlib";
|
||||
import * as crypto from "crypto";
|
||||
import * as fs from "fs";
|
||||
import { Socket } from "net";
|
||||
import * as auth from "./auth";
|
||||
import * as config from "./config";
|
||||
import * as common from "./common";
|
||||
import * as soap from "./soap";
|
||||
@@ -39,7 +40,8 @@ import {
|
||||
SetAcsRequest,
|
||||
SessionFault,
|
||||
Operation,
|
||||
Fault
|
||||
Fault,
|
||||
Expression
|
||||
} from "./types";
|
||||
|
||||
const MAX_CYCLES = 4;
|
||||
@@ -64,6 +66,46 @@ function throwError(err, httpResponse?): never {
|
||||
throw err;
|
||||
}
|
||||
|
||||
function authenticate(sessionContext: SessionContext): boolean {
|
||||
const authExpression: Expression = localCache.getConfigExpression(
|
||||
sessionContext.cacheSnapshot,
|
||||
"cwmp.auth"
|
||||
);
|
||||
if (!authExpression) return true;
|
||||
|
||||
let authentication;
|
||||
|
||||
if (sessionContext.httpRequest.headers["authorization"]) {
|
||||
authentication = auth.parseAuthorizationHeader(
|
||||
sessionContext.httpRequest.headers["authorization"]
|
||||
);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const res = evaluate(
|
||||
authExpression,
|
||||
sessionContext.configContext,
|
||||
now,
|
||||
(e: Expression): Expression => {
|
||||
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "AUTH") {
|
||||
if (authentication && authentication["method"] === "Basic") {
|
||||
return (
|
||||
authentication["username"] === e[2] &&
|
||||
authentication["password"] === e[3]
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
);
|
||||
|
||||
if (res && !Array.isArray(res)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function writeResponse(
|
||||
sessionContext: SessionContext,
|
||||
res,
|
||||
@@ -218,6 +260,7 @@ function recordFault(
|
||||
function inform(sessionContext, rpc): void {
|
||||
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) => {
|
||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||
sessionContext.state = 1;
|
||||
|
||||
const res = soap.response({
|
||||
id: rpc.id,
|
||||
@@ -845,6 +888,8 @@ export function onConnection(socket): void {
|
||||
);
|
||||
}, timeout + 1000).unref();
|
||||
|
||||
if (sessionContext.state === 0) return;
|
||||
|
||||
session.serialize(sessionContext, (err, sessionContextString) => {
|
||||
if (err) return void throwError(err);
|
||||
cache.set(
|
||||
@@ -979,9 +1024,23 @@ function cacheDueTasksAndFaultsAndOperations(
|
||||
);
|
||||
}
|
||||
|
||||
function reportBadState(sessionContext): void {
|
||||
logger.accessError({
|
||||
message: "Bad session state",
|
||||
sessionContext: sessionContext
|
||||
});
|
||||
currentSessions.delete(sessionContext.httpResponse.connection);
|
||||
sessionContext.httpResponse.writeHead(400, { Connection: "close" });
|
||||
sessionContext.httpResponse.end("Bad session state");
|
||||
stats.concurrentRequests -= 1;
|
||||
}
|
||||
|
||||
function processRequest(sessionContext, rpc): void {
|
||||
if (rpc.cpeRequest) {
|
||||
if (rpc.cpeRequest.name === "Inform") {
|
||||
if (sessionContext.state !== 0)
|
||||
return void reportBadState(sessionContext);
|
||||
|
||||
logger.accessInfo({
|
||||
sessionContext: sessionContext,
|
||||
message: "Inform",
|
||||
@@ -989,6 +1048,9 @@ function processRequest(sessionContext, rpc): void {
|
||||
});
|
||||
inform(sessionContext, rpc);
|
||||
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
||||
if (sessionContext.state !== 1)
|
||||
return void reportBadState(sessionContext);
|
||||
|
||||
logger.accessInfo({
|
||||
sessionContext: sessionContext,
|
||||
message: "CPE request",
|
||||
@@ -996,6 +1058,9 @@ function processRequest(sessionContext, rpc): void {
|
||||
});
|
||||
transferComplete(sessionContext, rpc);
|
||||
} else if (rpc.cpeRequest.name === "GetRPCMethods") {
|
||||
if (sessionContext.state !== 1)
|
||||
return void reportBadState(sessionContext);
|
||||
|
||||
logger.accessInfo({
|
||||
sessionContext: sessionContext,
|
||||
message: "CPE request",
|
||||
@@ -1011,17 +1076,24 @@ function processRequest(sessionContext, rpc): void {
|
||||
});
|
||||
writeResponse(sessionContext, res);
|
||||
} else {
|
||||
if (sessionContext.state !== 1)
|
||||
return void reportBadState(sessionContext);
|
||||
|
||||
return void throwError(
|
||||
new Error("ACS method not supported"),
|
||||
sessionContext.httpResponse
|
||||
);
|
||||
}
|
||||
} else if (rpc.cpeResponse) {
|
||||
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
||||
|
||||
session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse, err => {
|
||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||
nextRpc(sessionContext);
|
||||
});
|
||||
} else if (rpc.cpeFault) {
|
||||
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
||||
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "CPE fault",
|
||||
@@ -1038,6 +1110,9 @@ function processRequest(sessionContext, rpc): void {
|
||||
});
|
||||
} else {
|
||||
// CPE sent empty response
|
||||
if (sessionContext.state !== 1) return void reportBadState(sessionContext);
|
||||
|
||||
sessionContext.state = 2;
|
||||
session.timeoutOperations(sessionContext, (err, faults, operations) => {
|
||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||
|
||||
@@ -1203,16 +1278,15 @@ export function listener(httpRequest, httpResponse): void {
|
||||
}
|
||||
|
||||
if (sessionContext) {
|
||||
if (
|
||||
(rpc.cpeRequest && rpc.cpeRequest.name === "Inform") ||
|
||||
!sessionContext.rpcRequest !== !(rpc.cpeResponse || rpc.cpeFault)
|
||||
) {
|
||||
if (sessionContext.state === 0 && !authenticate(sessionContext)) {
|
||||
logger.accessError({
|
||||
message: "Bad session state",
|
||||
message: "Authentication failure",
|
||||
sessionContext: sessionContext
|
||||
});
|
||||
httpResponse.writeHead(400, { Connection: "close" });
|
||||
httpResponse.end("Bad session state");
|
||||
|
||||
httpResponse.writeHead(401);
|
||||
httpResponse.end("Unauthorized");
|
||||
currentSessions.set(httpRequest.connection, sessionContext);
|
||||
stats.concurrentRequests -= 1;
|
||||
return;
|
||||
}
|
||||
@@ -1296,6 +1370,20 @@ export function listener(httpRequest, httpResponse): void {
|
||||
_sessionContext.retries[k] = v.retries;
|
||||
}
|
||||
}
|
||||
|
||||
if (!authenticate(_sessionContext)) {
|
||||
logger.accessError({
|
||||
message: "Authentication failure",
|
||||
sessionContext: _sessionContext
|
||||
});
|
||||
|
||||
httpResponse.writeHead(401);
|
||||
httpResponse.end("Unauthorized");
|
||||
currentSessions.set(httpRequest.connection, _sessionContext);
|
||||
stats.concurrentRequests -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
parsedRpc(_sessionContext, rpc, parseWarnings);
|
||||
}
|
||||
);
|
||||
|
||||
+2
-1
@@ -78,7 +78,8 @@ export function init(
|
||||
iteration: 0,
|
||||
cycle: 0,
|
||||
extensionsCache: {},
|
||||
declarations: []
|
||||
declarations: [],
|
||||
state: 0
|
||||
};
|
||||
|
||||
callback(null, sessionContext);
|
||||
|
||||
@@ -120,6 +120,7 @@ export interface SessionContext {
|
||||
toLoad?: Map<Path, number>;
|
||||
new?: boolean;
|
||||
debug?: boolean;
|
||||
state: number;
|
||||
tasks?: Task[];
|
||||
operations?: { [commandKey: string]: Operation };
|
||||
cacheUntil?: number;
|
||||
|
||||
Reference in new issue
Block a user