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";
|
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();
|
authHeader = authHeader.trim();
|
||||||
const method = authHeader.split(" ", 1)[0];
|
const method = authHeader.split(" ", 1)[0];
|
||||||
const res = { method: method };
|
const res = { method: method };
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ export async function httpConnectionRequest(
|
|||||||
if (res.statusCode === 200 || res.statusCode === 204) return;
|
if (res.statusCode === 200 || res.statusCode === 204) return;
|
||||||
|
|
||||||
if (res.statusCode === 401 && res.headers["www-authenticate"]) {
|
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);
|
[username, password, authExp] = extractAuth(authExp, context, now, false);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
+96
-8
@@ -21,6 +21,7 @@ import * as zlib from "zlib";
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { Socket } from "net";
|
import { Socket } from "net";
|
||||||
|
import * as auth from "./auth";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
import * as common from "./common";
|
import * as common from "./common";
|
||||||
import * as soap from "./soap";
|
import * as soap from "./soap";
|
||||||
@@ -39,7 +40,8 @@ import {
|
|||||||
SetAcsRequest,
|
SetAcsRequest,
|
||||||
SessionFault,
|
SessionFault,
|
||||||
Operation,
|
Operation,
|
||||||
Fault
|
Fault,
|
||||||
|
Expression
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const MAX_CYCLES = 4;
|
const MAX_CYCLES = 4;
|
||||||
@@ -64,6 +66,46 @@ function throwError(err, httpResponse?): never {
|
|||||||
throw err;
|
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(
|
function writeResponse(
|
||||||
sessionContext: SessionContext,
|
sessionContext: SessionContext,
|
||||||
res,
|
res,
|
||||||
@@ -218,6 +260,7 @@ function recordFault(
|
|||||||
function inform(sessionContext, rpc): void {
|
function inform(sessionContext, rpc): void {
|
||||||
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) => {
|
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) => {
|
||||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||||
|
sessionContext.state = 1;
|
||||||
|
|
||||||
const res = soap.response({
|
const res = soap.response({
|
||||||
id: rpc.id,
|
id: rpc.id,
|
||||||
@@ -845,6 +888,8 @@ export function onConnection(socket): void {
|
|||||||
);
|
);
|
||||||
}, timeout + 1000).unref();
|
}, timeout + 1000).unref();
|
||||||
|
|
||||||
|
if (sessionContext.state === 0) return;
|
||||||
|
|
||||||
session.serialize(sessionContext, (err, sessionContextString) => {
|
session.serialize(sessionContext, (err, sessionContextString) => {
|
||||||
if (err) return void throwError(err);
|
if (err) return void throwError(err);
|
||||||
cache.set(
|
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 {
|
function processRequest(sessionContext, rpc): void {
|
||||||
if (rpc.cpeRequest) {
|
if (rpc.cpeRequest) {
|
||||||
if (rpc.cpeRequest.name === "Inform") {
|
if (rpc.cpeRequest.name === "Inform") {
|
||||||
|
if (sessionContext.state !== 0)
|
||||||
|
return void reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "Inform",
|
message: "Inform",
|
||||||
@@ -989,6 +1048,9 @@ function processRequest(sessionContext, rpc): void {
|
|||||||
});
|
});
|
||||||
inform(sessionContext, rpc);
|
inform(sessionContext, rpc);
|
||||||
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
} else if (rpc.cpeRequest.name === "TransferComplete") {
|
||||||
|
if (sessionContext.state !== 1)
|
||||||
|
return void reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE request",
|
message: "CPE request",
|
||||||
@@ -996,6 +1058,9 @@ function processRequest(sessionContext, rpc): void {
|
|||||||
});
|
});
|
||||||
transferComplete(sessionContext, rpc);
|
transferComplete(sessionContext, rpc);
|
||||||
} else if (rpc.cpeRequest.name === "GetRPCMethods") {
|
} else if (rpc.cpeRequest.name === "GetRPCMethods") {
|
||||||
|
if (sessionContext.state !== 1)
|
||||||
|
return void reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE request",
|
message: "CPE request",
|
||||||
@@ -1011,17 +1076,24 @@ function processRequest(sessionContext, rpc): void {
|
|||||||
});
|
});
|
||||||
writeResponse(sessionContext, res);
|
writeResponse(sessionContext, res);
|
||||||
} else {
|
} else {
|
||||||
|
if (sessionContext.state !== 1)
|
||||||
|
return void reportBadState(sessionContext);
|
||||||
|
|
||||||
return void throwError(
|
return void throwError(
|
||||||
new Error("ACS method not supported"),
|
new Error("ACS method not supported"),
|
||||||
sessionContext.httpResponse
|
sessionContext.httpResponse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (rpc.cpeResponse) {
|
} else if (rpc.cpeResponse) {
|
||||||
|
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
||||||
|
|
||||||
session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse, err => {
|
session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse, err => {
|
||||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||||
nextRpc(sessionContext);
|
nextRpc(sessionContext);
|
||||||
});
|
});
|
||||||
} else if (rpc.cpeFault) {
|
} else if (rpc.cpeFault) {
|
||||||
|
if (sessionContext.state !== 2) return void reportBadState(sessionContext);
|
||||||
|
|
||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
sessionContext: sessionContext,
|
sessionContext: sessionContext,
|
||||||
message: "CPE fault",
|
message: "CPE fault",
|
||||||
@@ -1038,6 +1110,9 @@ function processRequest(sessionContext, rpc): void {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// CPE sent empty response
|
// CPE sent empty response
|
||||||
|
if (sessionContext.state !== 1) return void reportBadState(sessionContext);
|
||||||
|
|
||||||
|
sessionContext.state = 2;
|
||||||
session.timeoutOperations(sessionContext, (err, faults, operations) => {
|
session.timeoutOperations(sessionContext, (err, faults, operations) => {
|
||||||
if (err) return void throwError(err, sessionContext.httpResponse);
|
if (err) return void throwError(err, sessionContext.httpResponse);
|
||||||
|
|
||||||
@@ -1203,16 +1278,15 @@ export function listener(httpRequest, httpResponse): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sessionContext) {
|
if (sessionContext) {
|
||||||
if (
|
if (sessionContext.state === 0 && !authenticate(sessionContext)) {
|
||||||
(rpc.cpeRequest && rpc.cpeRequest.name === "Inform") ||
|
|
||||||
!sessionContext.rpcRequest !== !(rpc.cpeResponse || rpc.cpeFault)
|
|
||||||
) {
|
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
message: "Bad session state",
|
message: "Authentication failure",
|
||||||
sessionContext: sessionContext
|
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;
|
stats.concurrentRequests -= 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1296,6 +1370,20 @@ export function listener(httpRequest, httpResponse): void {
|
|||||||
_sessionContext.retries[k] = v.retries;
|
_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);
|
parsedRpc(_sessionContext, rpc, parseWarnings);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-1
@@ -78,7 +78,8 @@ export function init(
|
|||||||
iteration: 0,
|
iteration: 0,
|
||||||
cycle: 0,
|
cycle: 0,
|
||||||
extensionsCache: {},
|
extensionsCache: {},
|
||||||
declarations: []
|
declarations: [],
|
||||||
|
state: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
callback(null, sessionContext);
|
callback(null, sessionContext);
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export interface SessionContext {
|
|||||||
toLoad?: Map<Path, number>;
|
toLoad?: Map<Path, number>;
|
||||||
new?: boolean;
|
new?: boolean;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
state: number;
|
||||||
tasks?: Task[];
|
tasks?: Task[];
|
||||||
operations?: { [commandKey: string]: Operation };
|
operations?: { [commandKey: string]: Operation };
|
||||||
cacheUntil?: number;
|
cacheUntil?: number;
|
||||||
|
|||||||
Reference in new issue
Block a user