Add support for CPE authentication

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-06-05 00:17:36 -07:00
1 parent 53032c83f1
commit ae3779c484
5 files changed
+123 -11

No files matched your search

+21 -1
View File
@@ -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 };
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -78,7 +78,8 @@ export function init(
iteration: 0,
cycle: 0,
extensionsCache: {},
declarations: []
declarations: [],
state: 0
};
callback(null, sessionContext);
+1
View File
@@ -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;