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"; 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 };
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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;