mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralsec.git
synced 2025-10-30 02:12:32 +00:00
Initial
This commit is contained in:
65
CMakeLists.txt
Normal file
65
CMakeLists.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(ucentralsec)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(UNIX AND APPLE)
|
||||
set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)
|
||||
set(MYSQL_ROOT_DIR /usr/local/opt/mysql-client)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(PostgreSQL_TYPE_INCLUDE_DIR /usr/include/postgresql)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
|
||||
endif()
|
||||
|
||||
if(SMALL_BUILD)
|
||||
add_definitions(-DSMALL_BUILD)
|
||||
endif()
|
||||
|
||||
# Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build)
|
||||
file(READ build BUILD_NUM)
|
||||
if(BUILD_INCREMENT)
|
||||
MATH(EXPR BUILD_NUM "${BUILD_NUM}+1")
|
||||
file(WRITE build ${BUILD_NUM})
|
||||
endif()
|
||||
else()
|
||||
set(BUILD_NUM 1)
|
||||
file(WRITE build ${BUILD_NUM})
|
||||
endif()
|
||||
|
||||
set(BUILD_SHARED_LIBS 1)
|
||||
|
||||
add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}")
|
||||
|
||||
set(Boost_USE_STATIC_LIBS OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
|
||||
find_package(Boost REQUIRED system)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(CppKafka REQUIRED)
|
||||
find_package(PostgreSQL REQUIRED)
|
||||
find_package(MySQL REQUIRED)
|
||||
find_package(ODBC REQUIRED)
|
||||
find_package(Poco REQUIRED COMPONENTS Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL DataODBC)
|
||||
|
||||
include_directories(/usr/local/include /usr/local/opt/openssl/include src include/kafka)
|
||||
|
||||
add_executable( ucentralsec build
|
||||
src/Daemon.h src/Daemon.cpp
|
||||
src/SubSystemServer.cpp src/SubSystemServer.h
|
||||
src/RESTAPI_unknownRequestHandler.h src/RESTAPI_unknownRequestHandler.cpp
|
||||
src/RESTAPI_oauth2Handler.h src/RESTAPI_oauth2Handler.cpp
|
||||
src/RESTAPI_handler.h src/RESTAPI_handler.cpp
|
||||
src/RESTAPI_server.cpp src/RESTAPI_server.h
|
||||
src/RESTAPI_objects.cpp src/RESTAPI_objects.h
|
||||
src/RESTAPI_protocol.h
|
||||
src/AuthService.h src/AuthService.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ucentralsec PUBLIC
|
||||
${Poco_LIBRARIES} ${Boost_LIBRARIES} ${ZLIB_LIBRARIES} ${AWSSDK_LINK_LIBRARIES} CppKafka::cppkafka )
|
||||
274
openpapi/ucentralsec/ucentralsec.yaml
Normal file
274
openpapi/ucentralsec/ucentralsec.yaml
Normal file
@@ -0,0 +1,274 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: uCentral Security API
|
||||
description: A process to manage security logins
|
||||
version: 0.0.1
|
||||
license:
|
||||
name: BSD3
|
||||
url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
contact:
|
||||
name: Arilia Support
|
||||
email: ucentralsupport@arilia.com
|
||||
url: https://www.ucentral.info/support
|
||||
|
||||
servers:
|
||||
- url: 'https://localhost:16001/api/v1'
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- ApiKeyAuth: []
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-API-KEY
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
responses:
|
||||
NotFound:
|
||||
description: The specified resource was not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericErrorResponse'
|
||||
|
||||
Unauthorized:
|
||||
description: The requested does not have sufficient rights to perform the operation.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericErrorResponse'
|
||||
|
||||
Success:
|
||||
description: The requested operation was performed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericGoodAnswer'
|
||||
|
||||
CommandSubmitSuccess:
|
||||
description: The command was submitted succesfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
serialNumber:
|
||||
type: string
|
||||
UUID:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
schemas:
|
||||
|
||||
GenericErrorResponse:
|
||||
description: Typical error response
|
||||
properties:
|
||||
ErrorCode:
|
||||
type: integer
|
||||
ErrorDetails:
|
||||
type: string
|
||||
ErrorDescription:
|
||||
type: string
|
||||
|
||||
GenericGoodAnswer:
|
||||
description: used for all succesful responses.
|
||||
properties:
|
||||
Operation:
|
||||
type: string
|
||||
Details:
|
||||
type: string
|
||||
Code:
|
||||
type: integer
|
||||
|
||||
WebTokenRequest:
|
||||
description: User Id and password.
|
||||
type: object
|
||||
required:
|
||||
- userId
|
||||
- password
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
default: support@example.com
|
||||
password:
|
||||
type: string
|
||||
default: support
|
||||
refreshToken:
|
||||
type: string
|
||||
example:
|
||||
userId: support@example.com
|
||||
password: support
|
||||
|
||||
WebTokenResult:
|
||||
description: Login and Refresh Tokens to be used in subsequent API calls.
|
||||
type: object
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
token_type:
|
||||
type: string
|
||||
expires_in:
|
||||
type: integer
|
||||
format: int32
|
||||
idle_timeout:
|
||||
type: integer
|
||||
format: int32
|
||||
username:
|
||||
type: string
|
||||
created:
|
||||
type: integer
|
||||
format: int64
|
||||
aclTemplate:
|
||||
$ref: '#/components/schemas/WebTokenAclTemplate'
|
||||
|
||||
WebTokenAclTemplate:
|
||||
type: object
|
||||
properties:
|
||||
aclTemplate:
|
||||
$ref: '#/components/schemas/AclTemplate'
|
||||
|
||||
ApiKeyCreationRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
expiresOn:
|
||||
type: integer
|
||||
format: int64
|
||||
rights:
|
||||
$ref: '#/components/schemas/AclTemplate'
|
||||
|
||||
ApiKeyCreationAnswer:
|
||||
type: object
|
||||
properties:
|
||||
UUID:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
created:
|
||||
type: integer
|
||||
format: int64
|
||||
expiresOn:
|
||||
type: integer
|
||||
format: int64
|
||||
apiKey:
|
||||
type: string
|
||||
rights:
|
||||
$ref: '#/components/schemas/AclTemplate'
|
||||
|
||||
AclTemplate:
|
||||
type: object
|
||||
properties:
|
||||
Read:
|
||||
type: boolean
|
||||
ReadWrite:
|
||||
type: boolean
|
||||
ReadWriteCreate:
|
||||
type: boolean
|
||||
Delete:
|
||||
type: boolean
|
||||
PortalLogin:
|
||||
type: boolean
|
||||
|
||||
SystemEndpoint:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
vendor:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
format: uri
|
||||
authenticationType:
|
||||
type: string
|
||||
|
||||
SystemEndpointList:
|
||||
type: object
|
||||
properties:
|
||||
endpoints:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SystemEndpoint'
|
||||
|
||||
|
||||
paths:
|
||||
/oauth2:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Get access token - to be used as Bearer token header for all other API requests.
|
||||
operationId: getAccessToken
|
||||
requestBody:
|
||||
description: User id and password
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebTokenRequest'
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WebTokenResult'
|
||||
403:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
404:
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/oauth2/{token}:
|
||||
delete:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Revoke a token.
|
||||
operationId: removeAccessToken
|
||||
parameters:
|
||||
- in: path
|
||||
name: token
|
||||
schema:
|
||||
type:
|
||||
string
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericGoodAnswer'
|
||||
403:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
404:
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/systemInfo:
|
||||
get:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: retrieve the system layout
|
||||
operationId: getSystemInfo
|
||||
responses:
|
||||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SystemEndpointList'
|
||||
403:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
404:
|
||||
$ref: '#/components/responses/NotFound'
|
||||
101
src/ALBHealthCheckServer.h
Normal file
101
src/ALBHealthCheckServer.h
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Created by stephane bourque on 2021-06-04.
|
||||
//
|
||||
|
||||
#ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||
#define UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "Poco/Thread.h"
|
||||
#include "Poco/Net/HTTPServer.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/Net/HTTPRequestHandler.h"
|
||||
#include "Poco/Logger.h"
|
||||
|
||||
#include "Daemon.h"
|
||||
|
||||
namespace uCentral::ALBHealthCheck {
|
||||
|
||||
class ALBRequestHandler: public Poco::Net::HTTPRequestHandler
|
||||
/// Return a HTML document with the current date and time.
|
||||
{
|
||||
public:
|
||||
ALBRequestHandler(Poco::Logger & L)
|
||||
: Logger_(L)
|
||||
{
|
||||
}
|
||||
|
||||
void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response)
|
||||
{
|
||||
Logger_.information(Poco::format("ALB-REQUEST(%s): New ALB request.",Request.clientAddress().toString()));
|
||||
Response.setChunkedTransferEncoding(true);
|
||||
Response.setContentType("text/html");
|
||||
Response.setDate(Poco::Timestamp());
|
||||
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
Response.setKeepAlive(true);
|
||||
Response.set("Connection","keep-alive");
|
||||
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
std::ostream &Answer = Response.send();
|
||||
Answer << "uCentralGW Alive and kicking!" ;
|
||||
}
|
||||
|
||||
private:
|
||||
Poco::Logger & Logger_;
|
||||
};
|
||||
|
||||
class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory
|
||||
{
|
||||
public:
|
||||
explicit ALBRequestHandlerFactory(Poco::Logger & L):
|
||||
Logger_(L)
|
||||
{
|
||||
}
|
||||
|
||||
ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request)
|
||||
{
|
||||
if (request.getURI() == "/")
|
||||
return new ALBRequestHandler(Logger_);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Poco::Logger &Logger_;
|
||||
};
|
||||
|
||||
class Service {
|
||||
public:
|
||||
explicit Service(Poco::Logger &L)
|
||||
: Logger_(L) {};
|
||||
|
||||
int Start() {
|
||||
if(uCentral::ServiceConfig::GetBool("nlb.enable",false)) {
|
||||
Port_ = (int)uCentral::ServiceConfig::GetInt("nlb.port",15015);
|
||||
Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_);
|
||||
auto Params = new Poco::Net::HTTPServerParams;
|
||||
Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger_), *Socket_, Params);
|
||||
Server_->start();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if(Server_)
|
||||
Server_->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Poco::Net::HTTPServer> Server_;
|
||||
std::unique_ptr<Poco::Net::ServerSocket> Socket_;
|
||||
int Port_ = 0;
|
||||
Poco::Logger &Logger_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H
|
||||
235
src/AuthService.cpp
Normal file
235
src/AuthService.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "Poco/Net/OAuth20Credentials.h"
|
||||
#include "Poco/JWT/Token.h"
|
||||
#include "Poco/JWT/Signer.h"
|
||||
|
||||
#include "Daemon.h"
|
||||
#include "RESTAPI_handler.h"
|
||||
#include "AuthService.h"
|
||||
#include "uStorageService.h"
|
||||
#include "uUtils.h"
|
||||
|
||||
namespace uCentral::Auth {
|
||||
Service *Service::instance_ = nullptr;
|
||||
|
||||
ACCESS_TYPE IntToAccessType(int C) {
|
||||
switch (C) {
|
||||
case 1: return USERNAME;
|
||||
case 2: return SERVER;
|
||||
case 3: return CUSTOM;
|
||||
default:
|
||||
return USERNAME;
|
||||
}
|
||||
}
|
||||
|
||||
int AccessTypeToInt(ACCESS_TYPE T) {
|
||||
switch (T) {
|
||||
case USERNAME: return 1;
|
||||
case SERVER: return 2;
|
||||
case CUSTOM: return 3;
|
||||
}
|
||||
return 1; // some compilers complain...
|
||||
}
|
||||
|
||||
Service::Service() noexcept: SubSystemServer("Authentication", "AUTH-SVR", "authentication")
|
||||
{
|
||||
std::string E{"SHA512"};
|
||||
}
|
||||
|
||||
int Start() {
|
||||
return Service::instance()->Start();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
Service::instance()->Stop();
|
||||
}
|
||||
|
||||
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo ) {
|
||||
return Service::instance()->IsAuthorized(Request,SessionToken, UserInfo);
|
||||
}
|
||||
|
||||
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken ) {
|
||||
return Service::instance()->Authorize(UserName,Password,ResultToken);
|
||||
}
|
||||
|
||||
void Logout(const std::string &Token) {
|
||||
Service::instance()->Logout(Token);
|
||||
}
|
||||
|
||||
int Service::Start() {
|
||||
Signer_.setRSAKey(uCentral::instance()->Key());
|
||||
Signer_.addAllAlgorithms();
|
||||
Logger_.notice("Starting...");
|
||||
Secure_ = uCentral::ServiceConfig::GetBool(SubSystemConfigPrefix_+".enabled",true);
|
||||
DefaultPassword_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".default.password","");
|
||||
DefaultUserName_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".default.username","");
|
||||
Mechanism_ = uCentral::ServiceConfig::GetString(SubSystemConfigPrefix_+".service.type","internal");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Service::Stop() {
|
||||
Logger_.notice("Stopping...");
|
||||
}
|
||||
|
||||
bool Service::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo )
|
||||
{
|
||||
if(!Secure_)
|
||||
return true;
|
||||
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
|
||||
std::string CallToken;
|
||||
|
||||
try {
|
||||
Poco::Net::OAuth20Credentials Auth(Request);
|
||||
|
||||
if (Auth.getScheme() == "Bearer") {
|
||||
CallToken = Auth.getBearerToken();
|
||||
}
|
||||
} catch(const Poco::Exception &E) {
|
||||
}
|
||||
|
||||
if(CallToken.empty())
|
||||
CallToken = Request.get("X-API-KEY ", "");
|
||||
|
||||
if(CallToken.empty())
|
||||
return false;
|
||||
|
||||
auto Client = Tokens_.find(CallToken);
|
||||
|
||||
if( Client == Tokens_.end() )
|
||||
return ValidateToken(CallToken, CallToken, UserInfo);
|
||||
|
||||
if((Client->second.created_ + Client->second.expires_in_) > time(nullptr)) {
|
||||
SessionToken = CallToken;
|
||||
UserInfo = Client->second ;
|
||||
return true;
|
||||
}
|
||||
|
||||
Tokens_.erase(CallToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Service::Logout(const std::string &token) {
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
Tokens_.erase(token);
|
||||
}
|
||||
|
||||
std::string Service::GenerateToken(const std::string & Identity, ACCESS_TYPE Type, int NumberOfDays) {
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
|
||||
Poco::JWT::Token T;
|
||||
|
||||
T.setType("JWT");
|
||||
switch(Type) {
|
||||
case USERNAME: T.setSubject("usertoken"); break;
|
||||
case SERVER: T.setSubject("servertoken"); break;
|
||||
case CUSTOM: T.setSubject("customtoken"); break;
|
||||
}
|
||||
|
||||
T.payload().set("identity", Identity);
|
||||
T.setIssuedAt(Poco::Timestamp());
|
||||
T.setExpiration(Poco::Timestamp() + Poco::Timespan(NumberOfDays,0,0,0,0));
|
||||
std::string JWT = Signer_.sign(T,Poco::JWT::Signer::ALGO_RS256);
|
||||
|
||||
return JWT;
|
||||
}
|
||||
|
||||
bool Service::ValidateToken(const std::string & Token, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo ) {
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
Poco::JWT::Token DecryptedToken;
|
||||
|
||||
try {
|
||||
if (Signer_.tryVerify(Token, DecryptedToken)) {
|
||||
auto Expires = DecryptedToken.getExpiration();
|
||||
if (Expires > Poco::Timestamp()) {
|
||||
auto Identity = DecryptedToken.payload().get("identity").toString();
|
||||
auto IssuedAt = DecryptedToken.getIssuedAt();
|
||||
auto Subject = DecryptedToken.getSubject();
|
||||
|
||||
UserInfo.access_token_ = Token;
|
||||
UserInfo.refresh_token_= Token;
|
||||
UserInfo.username_ = Identity;
|
||||
UserInfo.id_token_ = Token;
|
||||
UserInfo.token_type_ = "Bearer";
|
||||
UserInfo.created_ = IssuedAt.epochTime();
|
||||
UserInfo.expires_in_ = Expires.epochTime() - IssuedAt.epochTime();
|
||||
UserInfo.idle_timeout_ = 5*60;
|
||||
|
||||
if(uCentral::Storage::GetIdentityRights(Identity, UserInfo.acl_template_)) {
|
||||
} else {
|
||||
// we can get in but we have no given rights... something is very wrong
|
||||
UserInfo.acl_template_.Read_ = true ;
|
||||
UserInfo.acl_template_.ReadWriteCreate_ =
|
||||
UserInfo.acl_template_.ReadWrite_ =
|
||||
UserInfo.acl_template_.Delete_ = false;
|
||||
UserInfo.acl_template_.PortalLogin_ = true;
|
||||
}
|
||||
|
||||
Tokens_[UserInfo.access_token_] = UserInfo;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (const Poco::Exception &E ) {
|
||||
Logger_.log(E);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Service::CreateToken(const std::string & UserName, uCentral::Objects::WebToken & UserInfo, uCentral::Objects::AclTemplate & ACL)
|
||||
{
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
|
||||
std::string Token = GenerateToken(UserName,USERNAME,30);
|
||||
|
||||
UserInfo.acl_template_ = ACL;
|
||||
|
||||
UserInfo.expires_in_ = 30 * 24 * 60 * 60 ;
|
||||
UserInfo.idle_timeout_ = 5 * 60;
|
||||
UserInfo.token_type_ = "Bearer";
|
||||
UserInfo.access_token_ = Token;
|
||||
UserInfo.id_token_ = Token;
|
||||
UserInfo.refresh_token_ = Token;
|
||||
UserInfo.created_ = time(nullptr);
|
||||
UserInfo.username_ = UserName;
|
||||
|
||||
Tokens_[UserInfo.access_token_] = UserInfo;
|
||||
}
|
||||
|
||||
bool Service::Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken )
|
||||
{
|
||||
SubMutexGuard Guard(Mutex_);
|
||||
uCentral::Objects::AclTemplate ACL;
|
||||
|
||||
if(Mechanism_=="internal")
|
||||
{
|
||||
if(((UserName == DefaultUserName_) && (Password == DefaultPassword_)) || !Secure_)
|
||||
{
|
||||
ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
|
||||
CreateToken(UserName, ResultToken, ACL);
|
||||
return true;
|
||||
}
|
||||
} else if (Mechanism_=="db") {
|
||||
SHA2_.update(Password + UserName);
|
||||
auto EncryptedPassword = uCentral::Utils::ToHex(SHA2_.digest());
|
||||
|
||||
std::string TUser{UserName};
|
||||
if(uCentral::Storage::GetIdentity(TUser,EncryptedPassword,USERNAME,ACL)) {
|
||||
CreateToken(UserName, ResultToken, ACL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // end of namespace
|
||||
81
src/AuthService.h
Normal file
81
src/AuthService.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_UAUTHSERVICE_H
|
||||
#define UCENTRAL_UAUTHSERVICE_H
|
||||
|
||||
#include "SubSystemServer.h"
|
||||
|
||||
#include "Poco/JSON/Object.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/JWT/Signer.h"
|
||||
#include "Poco/SHA2Engine.h"
|
||||
|
||||
#include "RESTAPI_objects.h"
|
||||
|
||||
namespace uCentral::Auth {
|
||||
|
||||
enum ACCESS_TYPE {
|
||||
USERNAME,
|
||||
SERVER,
|
||||
CUSTOM
|
||||
};
|
||||
|
||||
ACCESS_TYPE IntToAccessType(int C);
|
||||
int AccessTypeToInt(ACCESS_TYPE T);
|
||||
|
||||
int Start();
|
||||
void Stop();
|
||||
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
|
||||
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
|
||||
void Logout(const std::string &token);
|
||||
|
||||
class Service : public SubSystemServer {
|
||||
public:
|
||||
|
||||
Service() noexcept;
|
||||
|
||||
friend int Start();
|
||||
friend void Stop();
|
||||
|
||||
static Service *instance() {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new Service;
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
friend bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
|
||||
friend bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
|
||||
[[nodiscard]] std::string GenerateToken(const std::string & UserName, ACCESS_TYPE Type, int NumberOfDays);
|
||||
[[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, struct uCentral::Objects::WebToken & UserInfo );
|
||||
friend void Logout(const std::string &token);
|
||||
|
||||
private:
|
||||
static Service *instance_;
|
||||
std::map<std::string,uCentral::Objects::WebToken> Tokens_;
|
||||
bool Secure_ = false ;
|
||||
std::string DefaultUserName_;
|
||||
std::string DefaultPassword_;
|
||||
std::string Mechanism_;
|
||||
bool AutoProvisioning_ = false ;
|
||||
Poco::JWT::Signer Signer_;
|
||||
Poco::SHA2Engine SHA2_;
|
||||
|
||||
int Start() override;
|
||||
void Stop() override;
|
||||
bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, struct uCentral::Objects::WebToken & UserInfo );
|
||||
void CreateToken(const std::string & UserName, uCentral::Objects::WebToken & ResultToken, uCentral::Objects::AclTemplate & ACL);
|
||||
bool Authorize( const std::string & UserName, const std::string & Password, uCentral::Objects::WebToken & ResultToken );
|
||||
void Logout(const std::string &token);
|
||||
};
|
||||
|
||||
} // end of namespace
|
||||
|
||||
#endif //UCENTRAL_UAUTHSERVICE_H
|
||||
277
src/Daemon.cpp
Normal file
277
src/Daemon.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// Created by stephane bourque on 2021-06-10.
|
||||
//
|
||||
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "Poco/Util/Application.h"
|
||||
#include "Poco/Util/ServerApplication.h"
|
||||
#include "Poco/Util/Option.h"
|
||||
#include "Poco/Util/OptionSet.h"
|
||||
#include "Poco/Util/HelpFormatter.h"
|
||||
#include "Poco/Environment.h"
|
||||
#include "Poco/Path.h"
|
||||
#include "Poco/Net/SSLManager.h"
|
||||
|
||||
#include "uUtils.h"
|
||||
|
||||
#include "ALBHealthCheckServer.h"
|
||||
#include "Daemon.h"
|
||||
|
||||
namespace uCentral {
|
||||
Daemon *Daemon::instance_ = nullptr;
|
||||
|
||||
Daemon * instance() { return uCentral::Daemon::instance(); }
|
||||
|
||||
void MyErrorHandler::exception(const Poco::Exception & E) {
|
||||
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||
instance()->logger().log(E);
|
||||
instance()->logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
|
||||
}
|
||||
|
||||
void MyErrorHandler::exception(const std::exception & E) {
|
||||
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||
instance()->logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
|
||||
}
|
||||
|
||||
void MyErrorHandler::exception() {
|
||||
Poco::Thread * CurrentThread = Poco::Thread::current();
|
||||
instance()->logger().warning(Poco::format("exception on %s",CurrentThread->getName()));
|
||||
}
|
||||
|
||||
void Daemon::Exit(int Reason) {
|
||||
std::exit(Reason);
|
||||
}
|
||||
|
||||
void Daemon::initialize(Application &self) {
|
||||
|
||||
Poco::Net::initializeSSL();
|
||||
|
||||
std::string Location = Poco::Environment::get(uCentral::DAEMON_CONFIG_ENV_VAR,".");
|
||||
Poco::Path ConfigFile;
|
||||
|
||||
ConfigFile = ConfigFileName_.empty() ? Location + "/" + uCentral::DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
|
||||
|
||||
if(!ConfigFile.isFile())
|
||||
{
|
||||
std::cerr << uCentral::DAEMON_APP_NAME << ": Configuration " << ConfigFile.toString() << " does not seem to exist. Please set "
|
||||
<< uCentral::DAEMON_ROOT_ENV_VAR << " env variable the path of the "
|
||||
<< uCentral::DAEMON_PROPERTIES_FILENAME << " file." << std::endl;
|
||||
std::exit(Poco::Util::Application::EXIT_CONFIG);
|
||||
}
|
||||
|
||||
static const char * LogFilePathKey = "logging.channels.c2.path";
|
||||
|
||||
loadConfiguration(ConfigFile.toString());
|
||||
|
||||
if(LogDir_.empty()) {
|
||||
std::string OriginalLogFileValue = config().getString(LogFilePathKey);
|
||||
std::string RealLogFileValue = Poco::Path::expand(OriginalLogFileValue);
|
||||
config().setString(LogFilePathKey, RealLogFileValue);
|
||||
} else {
|
||||
config().setString(LogFilePathKey, LogDir_);
|
||||
}
|
||||
|
||||
Poco::Path DataDir(config().getString("system.directory.data"));
|
||||
try {
|
||||
DataDir.makeDirectory();
|
||||
DataDir_ = DataDir.toString();
|
||||
} catch(...) {
|
||||
}
|
||||
|
||||
std::string KeyFile = Poco::Path::expand(config().getString("ucentral.service.key"));
|
||||
|
||||
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, ""));
|
||||
|
||||
ServerApplication::initialize(self);
|
||||
|
||||
logger().information("Starting...");
|
||||
|
||||
if(!DebugMode_)
|
||||
DebugMode_ = config().getBool("ucentral.system.debug",false);
|
||||
ID_ = config().getInt64("ucentral.system.id",1);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string Daemon::IdentifyDevice(const std::string & Id ) const {
|
||||
for(const auto &[Type,List]:DeviceTypeIdentifications_)
|
||||
{
|
||||
if(List.find(Id)!=List.end())
|
||||
return Type;
|
||||
}
|
||||
return "AP";
|
||||
}
|
||||
|
||||
void Daemon::uninitialize() {
|
||||
// add your own uninitialization code here
|
||||
ServerApplication::uninitialize();
|
||||
}
|
||||
|
||||
void Daemon::reinitialize(Poco::Util::Application &self) {
|
||||
ServerApplication::reinitialize(self);
|
||||
// add your own reinitialization code here
|
||||
}
|
||||
|
||||
void Daemon::defineOptions(Poco::Util::OptionSet &options) {
|
||||
ServerApplication::defineOptions(options);
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("help", "", "display help information on command line arguments")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleHelp)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("file", "", "specify the configuration file")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.argument("file")
|
||||
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleConfig)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("debug", "", "to run in debug, set to true")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleDebug)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.argument("dir")
|
||||
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleLogs)));
|
||||
|
||||
options.addOption(
|
||||
Poco::Util::Option("version", "", "get the version and quit.")
|
||||
.required(false)
|
||||
.repeatable(false)
|
||||
.callback(Poco::Util::OptionCallback<Daemon>(this, &Daemon::handleVersion)));
|
||||
|
||||
}
|
||||
|
||||
std::string Daemon::Version() {
|
||||
std::string V = APP_VERSION;
|
||||
std::string B = BUILD_NUMBER;
|
||||
return V + "(" + B + ")";
|
||||
}
|
||||
|
||||
void Daemon::handleHelp(const std::string &name, const std::string &value) {
|
||||
HelpRequested_ = true;
|
||||
displayHelp();
|
||||
stopOptionsProcessing();
|
||||
}
|
||||
|
||||
void Daemon::handleVersion(const std::string &name, const std::string &value) {
|
||||
HelpRequested_ = true;
|
||||
std::cout << Version() << std::endl;
|
||||
stopOptionsProcessing();
|
||||
}
|
||||
|
||||
void Daemon::handleDebug(const std::string &name, const std::string &value) {
|
||||
if(value == "true")
|
||||
DebugMode_ = true ;
|
||||
}
|
||||
|
||||
void Daemon::handleLogs(const std::string &name, const std::string &value) {
|
||||
LogDir_ = value;
|
||||
}
|
||||
|
||||
void Daemon::handleConfig(const std::string &name, const std::string &value) {
|
||||
ConfigFileName_ = value;
|
||||
}
|
||||
|
||||
void Daemon::displayHelp() {
|
||||
Poco::Util::HelpFormatter helpFormatter(options());
|
||||
helpFormatter.setCommand(commandName());
|
||||
helpFormatter.setUsage("OPTIONS");
|
||||
helpFormatter.setHeader("A " + std::string(uCentral::DAEMON_APP_NAME) + " implementation for TIP.");
|
||||
helpFormatter.format(std::cout);
|
||||
}
|
||||
|
||||
std::string Daemon::CreateUUID() {
|
||||
return UUIDGenerator_.create().toString();
|
||||
}
|
||||
|
||||
int Daemon::main(const ArgVec &args) {
|
||||
|
||||
Poco::ErrorHandler::set(&AppErrorHandler_);
|
||||
|
||||
if (!HelpRequested_) {
|
||||
Poco::Logger &logger = Poco::Logger::get(uCentral::DAEMON_APP_NAME );
|
||||
logger.notice(Poco::format("Starting %s version %s.",std::string(uCentral::DAEMON_APP_NAME), Version()));
|
||||
|
||||
if(Poco::Net::Socket::supportsIPv6())
|
||||
logger.information("System supports IPv6.");
|
||||
else
|
||||
logger.information("System does NOT support IPv6.");
|
||||
|
||||
if (config().getBool("application.runAsDaemon", false))
|
||||
{
|
||||
logger.information("Starting as a daemon.");
|
||||
}
|
||||
|
||||
uCentral::ALBHealthCheck::Service NLB(logger);
|
||||
NLB.Start();
|
||||
instance()->waitForTerminationRequest();
|
||||
NLB.Stop();
|
||||
|
||||
logger.notice(Poco::format("Stopped %s...",std::string(uCentral::DAEMON_APP_NAME)));
|
||||
}
|
||||
|
||||
return Application::EXIT_OK;
|
||||
}
|
||||
|
||||
namespace ServiceConfig {
|
||||
|
||||
uint64_t GetInt(const std::string &Key,uint64_t Default) {
|
||||
return (uint64_t) instance()->config().getInt64(Key,Default);
|
||||
}
|
||||
|
||||
uint64_t GetInt(const std::string &Key) {
|
||||
return instance()->config().getInt(Key);
|
||||
}
|
||||
|
||||
uint64_t GetBool(const std::string &Key,bool Default) {
|
||||
return instance()->config().getBool(Key,Default);
|
||||
}
|
||||
|
||||
uint64_t GetBool(const std::string &Key) {
|
||||
return instance()->config().getBool(Key);
|
||||
}
|
||||
|
||||
std::string GetString(const std::string &Key,const std::string & Default) {
|
||||
std::string R = instance()->config().getString(Key, Default);
|
||||
return Poco::Path::expand(R);
|
||||
}
|
||||
|
||||
std::string GetString(const std::string &Key) {
|
||||
std::string R = instance()->config().getString(Key);
|
||||
return Poco::Path::expand(R);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
try {
|
||||
|
||||
auto App = uCentral::Daemon::instance();
|
||||
auto ExitCode = App->run(argc, argv);
|
||||
delete App;
|
||||
|
||||
return ExitCode;
|
||||
|
||||
} catch (Poco::Exception &exc) {
|
||||
std::cerr << exc.displayText() << std::endl;
|
||||
return Poco::Util::Application::EXIT_SOFTWARE;
|
||||
}
|
||||
}
|
||||
|
||||
// end of namespace
|
||||
99
src/Daemon.h
Normal file
99
src/Daemon.h
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Created by stephane bourque on 2021-06-10.
|
||||
//
|
||||
|
||||
#ifndef UCENTRALSEC_DAEMON_H
|
||||
#define UCENTRALSEC_DAEMON_H
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "Poco/Util/Application.h"
|
||||
#include "Poco/Util/ServerApplication.h"
|
||||
#include "Poco/Util/Option.h"
|
||||
#include "Poco/Util/OptionSet.h"
|
||||
#include "Poco/UUIDGenerator.h"
|
||||
#include "Poco/ErrorHandler.h"
|
||||
#include "Poco/Crypto/RSAKey.h"
|
||||
|
||||
using Poco::Util::ServerApplication;
|
||||
|
||||
namespace uCentral {
|
||||
|
||||
static const char * DAEMON_PROPERTIES_FILENAME = "ucentralsec.properties";
|
||||
static const char * DAEMON_ROOT_ENV_VAR = "UCENTRALSEC_ROOT";
|
||||
static const char * DAEMON_CONFIG_ENV_VAR = "UCENTRALSEC_CONFIG";
|
||||
static const char * DAEMON_APP_NAME = "uCentralSec";
|
||||
|
||||
class MyErrorHandler : public Poco::ErrorHandler {
|
||||
public:
|
||||
void exception(const Poco::Exception & E) override;
|
||||
void exception(const std::exception & E) override;
|
||||
void exception() override;
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
class Daemon : public ServerApplication {
|
||||
|
||||
public:
|
||||
int main(const ArgVec &args) override;
|
||||
void initialize(Application &self) override;
|
||||
void uninitialize() override;
|
||||
void reinitialize(Application &self) override;
|
||||
void defineOptions(Poco::Util::OptionSet &options) override;
|
||||
void handleHelp(const std::string &name, const std::string &value);
|
||||
void handleVersion(const std::string &name, const std::string &value);
|
||||
void handleDebug(const std::string &name, const std::string &value);
|
||||
void handleLogs(const std::string &name, const std::string &value);
|
||||
void handleConfig(const std::string &name, const std::string &value);
|
||||
void displayHelp();
|
||||
|
||||
std::string CreateUUID();
|
||||
[[nodiscard]] std::string IdentifyDevice(const std::string & Compatible) const;
|
||||
bool AutoProvisioning() const { return AutoProvisioning_ ; }
|
||||
bool Debug() const { return DebugMode_; }
|
||||
uint64_t ID() const { return ID_; }
|
||||
static bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
|
||||
static std::string Version();
|
||||
const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
|
||||
void Exit(int Reason);
|
||||
[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
|
||||
|
||||
static Daemon *instance() {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new Daemon;
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
private:
|
||||
static Daemon *instance_;
|
||||
bool HelpRequested_ = false;
|
||||
bool AutoProvisioning_ = false;
|
||||
std::map<std::string,std::set<std::string>> DeviceTypeIdentifications_;
|
||||
std::string ConfigFileName_;
|
||||
std::string LogDir_;
|
||||
bool DebugMode_ = false;
|
||||
uint64_t ID_ = 1;
|
||||
Poco::UUIDGenerator UUIDGenerator_;
|
||||
MyErrorHandler AppErrorHandler_;
|
||||
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_ = nullptr;
|
||||
std::string DataDir_;
|
||||
};
|
||||
|
||||
namespace ServiceConfig {
|
||||
uint64_t GetInt(const std::string &Key,uint64_t Default);
|
||||
uint64_t GetInt(const std::string &Key);
|
||||
std::string GetString(const std::string &Key,const std::string & Default);
|
||||
std::string GetString(const std::string &Key);
|
||||
uint64_t GetBool(const std::string &Key,bool Default);
|
||||
uint64_t GetBool(const std::string &Key);
|
||||
}
|
||||
|
||||
Daemon * instance();
|
||||
}
|
||||
|
||||
#endif //UCENTRALSEC_DAEMON_H
|
||||
319
src/RESTAPI_handler.cpp
Normal file
319
src/RESTAPI_handler.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <future>
|
||||
#include <numeric>
|
||||
#include <chrono>
|
||||
|
||||
#include "Poco/URI.h"
|
||||
#include "Poco/DateTimeParser.h"
|
||||
|
||||
#include "RESTAPI_handler.h"
|
||||
#include "AuthService.h"
|
||||
#include "RESTAPI_protocol.h"
|
||||
#include "uUtils.h"
|
||||
|
||||
#define DBG std::cout << __LINE__ << " " __FILE__ << std::endl;
|
||||
|
||||
namespace uCentral::RESTAPI {
|
||||
bool RESTAPIHandler::ParseBindings(const std::string & Request, const std::string & Path, BindingMap &bindings) {
|
||||
std::string Param, Value;
|
||||
|
||||
bindings.clear();
|
||||
std::vector<std::string> PathItems = uCentral::Utils::Split(Path,'/');
|
||||
std::vector<std::string> ParamItems = uCentral::Utils::Split(Request,'/');
|
||||
|
||||
if(PathItems.size()!=ParamItems.size())
|
||||
return false;
|
||||
|
||||
for(auto i=0;i!=PathItems.size();i++) {
|
||||
if (PathItems[i] != ParamItems[i]) {
|
||||
if (PathItems[i][0] == '{') {
|
||||
auto ParamName = PathItems[i].substr(1, PathItems[i].size() - 2);
|
||||
bindings[ParamName] = ParamItems[i];
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RESTAPIHandler::PrintBindings() {
|
||||
for (auto &[key, value] : Bindings_)
|
||||
std::cout << "Key = " << key << " Value= " << value << std::endl;
|
||||
}
|
||||
|
||||
void RESTAPIHandler::ParseParameters(Poco::Net::HTTPServerRequest &request) {
|
||||
|
||||
Poco::URI uri(request.getURI());
|
||||
Parameters_ = uri.getQueryParameters();
|
||||
}
|
||||
|
||||
static bool is_number(const std::string &s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
|
||||
}
|
||||
|
||||
static bool is_bool(const std::string &s) {
|
||||
if (s == "true" || s == "false")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t RESTAPIHandler::GetParameter(const std::string &Name, const uint64_t Default) {
|
||||
|
||||
for (const auto &i : Parameters_) {
|
||||
if (i.first == Name) {
|
||||
if (!is_number(i.second))
|
||||
return Default;
|
||||
return std::stoi(i.second);
|
||||
}
|
||||
}
|
||||
return Default;
|
||||
}
|
||||
|
||||
bool RESTAPIHandler::GetBoolParameter(const std::string &Name, bool Default) {
|
||||
|
||||
for (const auto &i : Parameters_) {
|
||||
if (i.first == Name) {
|
||||
if (!is_bool(i.second))
|
||||
return Default;
|
||||
return i.second == "true";
|
||||
}
|
||||
}
|
||||
return Default;
|
||||
}
|
||||
|
||||
std::string RESTAPIHandler::GetParameter(const std::string &Name, const std::string &Default) {
|
||||
for (const auto &i : Parameters_) {
|
||||
if (i.first == Name)
|
||||
return i.second;
|
||||
}
|
||||
return Default;
|
||||
}
|
||||
|
||||
const std::string &RESTAPIHandler::GetBinding(const std::string &Name, const std::string &Default) {
|
||||
auto E = Bindings_.find(Name);
|
||||
|
||||
if (E == Bindings_.end())
|
||||
return Default;
|
||||
|
||||
return E->second;
|
||||
}
|
||||
|
||||
static std::string MakeList(const std::vector<std::string> &L) {
|
||||
std::string Return;
|
||||
|
||||
for (const auto &i : L)
|
||||
if (Return.empty())
|
||||
Return = i;
|
||||
else
|
||||
Return += ", " + i;
|
||||
|
||||
return Return;
|
||||
}
|
||||
|
||||
void RESTAPIHandler::AddCORS(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
auto Origin = Request.find("Origin");
|
||||
if (Origin != Request.end()) {
|
||||
Response.set("Access-Control-Allow-Origin", Origin->second);
|
||||
Response.set("Vary", "Origin");
|
||||
} else {
|
||||
Response.set("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
Response.set("Access-Control-Allow-Headers", "*");
|
||||
Response.set("Access-Control-Allow-Methods", MakeList(Methods_));
|
||||
Response.set("Access-Control-Max-Age", "86400");
|
||||
}
|
||||
|
||||
void RESTAPIHandler::SetCommonHeaders(Poco::Net::HTTPServerResponse &Response, bool CloseConnection) {
|
||||
Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
|
||||
Response.setChunkedTransferEncoding(true);
|
||||
Response.setContentType("application/json");
|
||||
if(CloseConnection) {
|
||||
Response.set("Connection", "close");
|
||||
Response.setKeepAlive(false);
|
||||
} else {
|
||||
Response.setKeepAlive(true);
|
||||
Response.set("Connection", "Keep-Alive");
|
||||
Response.set("Keep-Alive", "timeout=5, max=1000");
|
||||
}
|
||||
}
|
||||
|
||||
void RESTAPIHandler::ProcessOptions(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
AddCORS(Request, Response);
|
||||
SetCommonHeaders(Response);
|
||||
Response.setContentLength(0);
|
||||
Response.set("Access-Control-Allow-Credentials", "true");
|
||||
Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
|
||||
Response.set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
|
||||
/* std::cout << "RESPONSE:" << std::endl;
|
||||
for(const auto &[f,s]:Response)
|
||||
std::cout << "First: " << f << " second:" << s << std::endl;
|
||||
*/
|
||||
Response.send();
|
||||
}
|
||||
|
||||
void RESTAPIHandler::PrepareResponse(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response,
|
||||
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||
bool CloseConnection) {
|
||||
Response.setStatus(Status);
|
||||
AddCORS(Request, Response);
|
||||
SetCommonHeaders(Response, CloseConnection);
|
||||
}
|
||||
|
||||
void RESTAPIHandler::BadRequest(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||
Response.send();
|
||||
}
|
||||
|
||||
void RESTAPIHandler::UnAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
|
||||
Response.send();
|
||||
}
|
||||
|
||||
void RESTAPIHandler::NotFound(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
PrepareResponse(Request, Response, Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
|
||||
Response.send();
|
||||
}
|
||||
|
||||
void RESTAPIHandler::OK(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
PrepareResponse(Request, Response);
|
||||
Response.send();
|
||||
}
|
||||
|
||||
void RESTAPIHandler::SendFile(Poco::File & File, const std::string & UUID, Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
|
||||
Response.set("Content-Type","application/octet-stream");
|
||||
Response.set("Content-Disposition", "attachment; filename=" + UUID );
|
||||
Response.set("Content-Transfer-Encoding","binary");
|
||||
Response.set("Accept-Ranges", "bytes");
|
||||
Response.set("Cache-Control", "private");
|
||||
Response.set("Pragma", "private");
|
||||
Response.set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
|
||||
Response.set("Content-Length", std::to_string(File.getSize()));
|
||||
AddCORS(Request, Response);
|
||||
Response.sendFile(File.path(),"application/octet-stream");
|
||||
}
|
||||
|
||||
void RESTAPIHandler::ReturnStatus(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response,
|
||||
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||
bool CloseConnection) {
|
||||
PrepareResponse(Request, Response, Status, CloseConnection);
|
||||
if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) {
|
||||
Response.setContentLength(0);
|
||||
Response.erase("Content-Type");
|
||||
Response.setChunkedTransferEncoding(false);
|
||||
}
|
||||
Response.send();
|
||||
}
|
||||
|
||||
bool RESTAPIHandler::ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
if (Request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
|
||||
/* std::cout << "REQUEST:" << std::endl;
|
||||
for(const auto &[f,s]:Request)
|
||||
std::cout << "First: " << f << " second:" << s << std::endl;
|
||||
*/
|
||||
ProcessOptions(Request, Response);
|
||||
return false;
|
||||
} else if (std::find(Methods_.begin(), Methods_.end(), Request.getMethod()) == Methods_.end()) {
|
||||
BadRequest(Request, Response);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RESTAPIHandler::IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
if (uCentral::Auth::IsAuthorized(Request, SessionToken_, UserInfo_)) {
|
||||
return true;
|
||||
} else {
|
||||
UnAuthorized(Request, Response);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RESTAPIHandler::IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response, std::string &UserName) {
|
||||
|
||||
if (uCentral::Auth::IsAuthorized(Request, SessionToken_, UserInfo_)) {
|
||||
UserName = UserInfo_.username_;
|
||||
return true;
|
||||
} else {
|
||||
UnAuthorized(Request, Response);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RESTAPIHandler::ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
auto Key = Request.get("X-API-KEY", "");
|
||||
|
||||
if (Key.empty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RESTAPIHandler::ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
|
||||
Poco::Net::HTTPServerResponse &Response) {
|
||||
PrepareResponse(Request, Response);
|
||||
std::ostream &Answer = Response.send();
|
||||
Poco::JSON::Stringifier::stringify(Object, Answer);
|
||||
}
|
||||
|
||||
void RESTAPIHandler::InitQueryBlock() {
|
||||
QB_.SerialNumber = GetParameter(uCentral::RESTAPI::Protocol::SERIALNUMBER, "");
|
||||
QB_.StartDate = GetParameter(uCentral::RESTAPI::Protocol::STARTDATE, 0);
|
||||
QB_.EndDate = GetParameter(uCentral::RESTAPI::Protocol::ENDDATE, 0);
|
||||
QB_.Offset = GetParameter(uCentral::RESTAPI::Protocol::OFFSET, 0);
|
||||
QB_.Limit = GetParameter(uCentral::RESTAPI::Protocol::LIMIT, 100);
|
||||
QB_.Filter = GetParameter(uCentral::RESTAPI::Protocol::FILTER, "");
|
||||
QB_.Select = GetParameter(uCentral::RESTAPI::Protocol::SELECT, "");
|
||||
QB_.Lifetime = GetBoolParameter(uCentral::RESTAPI::Protocol::LIFETIME,false);
|
||||
QB_.LogType = GetParameter(uCentral::RESTAPI::Protocol::LOGTYPE,0);
|
||||
QB_.LastOnly = GetBoolParameter(uCentral::RESTAPI::Protocol::LASTONLY,false);
|
||||
QB_.Newest = GetBoolParameter(uCentral::RESTAPI::Protocol::NEWEST,false);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t RESTAPIHandler::Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter);
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string RESTAPIHandler::GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter).toString();
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool RESTAPIHandler::GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default){
|
||||
if(Obj->has(Parameter))
|
||||
return Obj->get(Parameter).toString()=="true";
|
||||
return Default;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t RESTAPIHandler::GetWhen(const Poco::JSON::Object::Ptr &Obj) {
|
||||
return RESTAPIHandler::Get(uCentral::RESTAPI::Protocol::WHEN, Obj);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
107
src/RESTAPI_handler.h
Normal file
107
src/RESTAPI_handler.h
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_RESTAPI_HANDLER_H
|
||||
#define UCENTRAL_RESTAPI_HANDLER_H
|
||||
|
||||
#include "Poco/URI.h"
|
||||
#include "Poco/Net/HTTPRequestHandler.h"
|
||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/HTTPServerResponse.h"
|
||||
#include "Poco/Net/NetException.h"
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/File.h"
|
||||
#include "Poco/JSON/Object.h"
|
||||
|
||||
#include "RESTAPI_objects.h"
|
||||
#include "AuthService.h"
|
||||
|
||||
namespace uCentral::RESTAPI {
|
||||
|
||||
struct QueryBlock {
|
||||
uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ;
|
||||
std::string SerialNumber, Filter, Select;
|
||||
bool Lifetime=false, LastOnly=false, Newest=false;
|
||||
};
|
||||
|
||||
class RESTAPIHandler : public Poco::Net::HTTPRequestHandler {
|
||||
public:
|
||||
typedef std::map<std::string, std::string> BindingMap;
|
||||
|
||||
RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector<std::string> Methods)
|
||||
: Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)) {}
|
||||
|
||||
static bool ParseBindings(const std::string & Path, const std::string & Request, BindingMap &Keys);
|
||||
void PrintBindings();
|
||||
void ParseParameters(Poco::Net::HTTPServerRequest &request);
|
||||
|
||||
void AddCORS(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response);
|
||||
void SetCommonHeaders(Poco::Net::HTTPServerResponse &response, bool CloseConnection=false);
|
||||
void ProcessOptions(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &response);
|
||||
void
|
||||
PrepareResponse(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &response,
|
||||
Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
|
||||
bool CloseConnection = false);
|
||||
bool ContinueProcessing(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response);
|
||||
bool IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response);
|
||||
bool IsAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response, std::string &UserName);
|
||||
uint64_t GetParameter(const std::string &Name, uint64_t Default);
|
||||
std::string GetParameter(const std::string &Name, const std::string &Default);
|
||||
bool GetBoolParameter(const std::string &Name, bool Default);
|
||||
bool ValidateAPIKey(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response);
|
||||
|
||||
void BadRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||
void UnAuthorized(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response);
|
||||
void ReturnObject(Poco::Net::HTTPServerRequest &Request, Poco::JSON::Object &Object,
|
||||
Poco::Net::HTTPServerResponse &Response);
|
||||
void NotFound(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||
void OK(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||
void ReturnStatus(Poco::Net::HTTPServerRequest &Request,
|
||||
Poco::Net::HTTPServerResponse &Response,
|
||||
Poco::Net::HTTPResponse::HTTPStatus Status,
|
||||
bool CloseConnection=false);
|
||||
void SendFile(Poco::File & File, const std::string & UUID,
|
||||
Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response);
|
||||
|
||||
const std::string &GetBinding(const std::string &Name, const std::string &Default);
|
||||
void InitQueryBlock();
|
||||
|
||||
[[nodiscard]] inline bool HasReadAccess() const {
|
||||
return UserInfo_.acl_template_.Read_ || UserInfo_.acl_template_.ReadWrite_ ||
|
||||
UserInfo_.acl_template_.ReadWriteCreate_;
|
||||
}
|
||||
[[nodiscard]] inline bool HasWriteAccess() const {
|
||||
return UserInfo_.acl_template_.ReadWrite_ || UserInfo_.acl_template_.ReadWriteCreate_;
|
||||
}
|
||||
[[nodiscard]] inline bool HasCreateAccess() const {
|
||||
return UserInfo_.acl_template_.ReadWriteCreate_;
|
||||
}
|
||||
[[nodiscard]] static uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0);
|
||||
[[nodiscard]] static std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default="");
|
||||
[[nodiscard]] static bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false);
|
||||
[[nodiscard]] static uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj);
|
||||
|
||||
protected:
|
||||
BindingMap Bindings_;
|
||||
Poco::URI::QueryParameters Parameters_;
|
||||
Poco::Logger &Logger_;
|
||||
std::string SessionToken_;
|
||||
struct uCentral::Objects::WebToken UserInfo_;
|
||||
std::vector<std::string> Methods_;
|
||||
QueryBlock QB_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //UCENTRAL_RESTAPI_HANDLER_H
|
||||
60
src/RESTAPI_oauth2Handler.cpp
Normal file
60
src/RESTAPI_oauth2Handler.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "Poco/JSON/Parser.h"
|
||||
|
||||
#include "RESTAPI_oauth2Handler.h"
|
||||
#include "AuthService.h"
|
||||
#include "RESTAPI_protocol.h"
|
||||
|
||||
void RESTAPI_oauth2Handler::handleRequest(Poco::Net::HTTPServerRequest & Request, Poco::Net::HTTPServerResponse & Response)
|
||||
{
|
||||
if(!ContinueProcessing(Request,Response))
|
||||
return;
|
||||
|
||||
try {
|
||||
if (Request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_POST) {
|
||||
|
||||
// Extract the info for login...
|
||||
Poco::JSON::Parser parser;
|
||||
Poco::JSON::Object::Ptr Obj = parser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>();
|
||||
|
||||
auto userId = GetS(uCentral::RESTAPI::Protocol::USERID, Obj);
|
||||
auto password = GetS(uCentral::RESTAPI::Protocol::PASSWORD, Obj);
|
||||
|
||||
Poco::toLowerInPlace(userId);
|
||||
uCentral::Objects::WebToken Token;
|
||||
|
||||
if (uCentral::Auth::Authorize(userId, password, Token)) {
|
||||
Poco::JSON::Object ReturnObj;
|
||||
Token.to_json(ReturnObj);
|
||||
ReturnObject(Request, ReturnObj, Response);
|
||||
} else {
|
||||
UnAuthorized(Request, Response);
|
||||
}
|
||||
} else if (Request.getMethod() == Poco::Net::HTTPServerRequest::HTTP_DELETE) {
|
||||
if (!IsAuthorized(Request, Response)) {
|
||||
return;
|
||||
}
|
||||
auto Token = GetBinding(uCentral::RESTAPI::Protocol::TOKEN, "...");
|
||||
if (Token == SessionToken_) {
|
||||
uCentral::Auth::Logout(Token);
|
||||
ReturnStatus(Request, Response, Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true);
|
||||
} else {
|
||||
NotFound(Request,Response);
|
||||
}
|
||||
} else {
|
||||
BadRequest(Request, Response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (const Poco::Exception &E) {
|
||||
Logger_.warning(Poco::format( "%s: Failed with: %s" , std::string(__func__), E.displayText()));
|
||||
}
|
||||
BadRequest(Request, Response);
|
||||
}
|
||||
26
src/RESTAPI_oauth2Handler.h
Normal file
26
src/RESTAPI_oauth2Handler.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H
|
||||
#define UCENTRAL_RESTAPI_OAUTH2HANDLER_H
|
||||
|
||||
#include "RESTAPI_handler.h"
|
||||
|
||||
class RESTAPI_oauth2Handler: public uCentral::RESTAPI::RESTAPIHandler
|
||||
{
|
||||
public:
|
||||
RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
|
||||
: RESTAPIHandler(bindings,L,
|
||||
std::vector<std::string>
|
||||
{ Poco::Net::HTTPRequest::HTTP_POST,
|
||||
Poco::Net::HTTPRequest::HTTP_DELETE,
|
||||
Poco::Net::HTTPRequest::HTTP_OPTIONS}) {}
|
||||
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override;
|
||||
};
|
||||
|
||||
#endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H
|
||||
48
src/RESTAPI_objects.cpp
Normal file
48
src/RESTAPI_objects.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "Poco/JSON/Parser.h"
|
||||
#include "Poco/JSON/Stringifier.h"
|
||||
|
||||
#include "Daemon.h"
|
||||
#include "RESTAPI_handler.h"
|
||||
#include "RESTAPI_objects.h"
|
||||
#include "uUtils.h"
|
||||
|
||||
namespace uCentral::Objects {
|
||||
|
||||
void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) {
|
||||
std::string D = ObjStr.empty() ? "{}" : ObjStr;
|
||||
Poco::JSON::Parser P;
|
||||
Poco::Dynamic::Var result = P.parse(D);
|
||||
const auto &DetailsObj = result.extract<Poco::JSON::Object::Ptr>();
|
||||
Obj.set(ObjName, DetailsObj);
|
||||
}
|
||||
|
||||
void AclTemplate::to_json(Poco::JSON::Object &Obj) const {
|
||||
Obj.set("Read",Read_);
|
||||
Obj.set("ReadWrite",ReadWrite_);
|
||||
Obj.set("ReadWriteCreate",ReadWriteCreate_);
|
||||
Obj.set("Delete",Delete_);
|
||||
Obj.set("PortalLogin",PortalLogin_);
|
||||
}
|
||||
|
||||
void WebToken::to_json(Poco::JSON::Object & Obj) const {
|
||||
Poco::JSON::Object AclTemplateObj;
|
||||
acl_template_.to_json(AclTemplateObj);
|
||||
Obj.set("access_token",access_token_);
|
||||
Obj.set("refresh_token",refresh_token_);
|
||||
Obj.set("token_type",token_type_);
|
||||
Obj.set("expires_in",expires_in_);
|
||||
Obj.set("idle_timeout",idle_timeout_);
|
||||
Obj.set("created",created_);
|
||||
Obj.set("username",username_);
|
||||
Obj.set("aclTemplate",AclTemplateObj);
|
||||
}
|
||||
}
|
||||
|
||||
39
src/RESTAPI_objects.h
Normal file
39
src/RESTAPI_objects.h
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_RESTAPI_OBJECTS_H
|
||||
#define UCENTRAL_RESTAPI_OBJECTS_H
|
||||
|
||||
#include "Poco/JSON/Object.h"
|
||||
|
||||
namespace uCentral::Objects {
|
||||
|
||||
struct AclTemplate {
|
||||
bool Read_ = true ;
|
||||
bool ReadWrite_ = true ;
|
||||
bool ReadWriteCreate_ = true ;
|
||||
bool Delete_ = true ;
|
||||
bool PortalLogin_ = true ;
|
||||
void to_json(Poco::JSON::Object &Obj) const ;
|
||||
};
|
||||
|
||||
struct WebToken {
|
||||
std::string access_token_;
|
||||
std::string refresh_token_;
|
||||
std::string id_token_;
|
||||
std::string token_type_;
|
||||
std::string username_;
|
||||
unsigned int expires_in_;
|
||||
unsigned int idle_timeout_;
|
||||
AclTemplate acl_template_;
|
||||
uint64_t created_;
|
||||
void to_json(Poco::JSON::Object &Obj) const ;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //UCENTRAL_RESTAPI_OBJECTS_H
|
||||
86
src/RESTAPI_protocol.h
Normal file
86
src/RESTAPI_protocol.h
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||
#define UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||
|
||||
namespace uCentral::RESTAPI::Protocol {
|
||||
static const char * CAPABILITIES = "capabilities";
|
||||
static const char * LOGS = "logs";
|
||||
static const char * HEALTHCHECKS = "healthchecks";
|
||||
static const char * STATISTICS = "statistics";
|
||||
static const char * STATUS = "status";
|
||||
static const char * SERIALNUMBER = "serialNumber";
|
||||
static const char * PERFORM = "perform";
|
||||
static const char * CONFIGURE = "configure";
|
||||
static const char * UPGRADE = "upgrade";
|
||||
static const char * REBOOT = "reboot";
|
||||
static const char * FACTORY = "factory";
|
||||
static const char * LEDS = "leds";
|
||||
static const char * TRACE = "trace";
|
||||
static const char * REQUEST = "request";
|
||||
static const char * WIFISCAN = "wifiscan";
|
||||
static const char * EVENTQUEUE = "eventqueue";
|
||||
static const char * RTTY = "rtty";
|
||||
static const char * COMMAND = "command";
|
||||
static const char * STARTDATE = "startDate";
|
||||
static const char * ENDDATE = "endDate";
|
||||
static const char * OFFSET = "offset";
|
||||
static const char * LIMIT = "limit";
|
||||
static const char * LIFETIME = "lifetime";
|
||||
static const char * UUID = "UUID";
|
||||
static const char * DATA = "data";
|
||||
static const char * CONFIGURATION = "configuration";
|
||||
static const char * WHEN = "when";
|
||||
static const char * URI = "uri";
|
||||
static const char * LOGTYPE = "logType";
|
||||
static const char * VALUES = "values";
|
||||
static const char * TYPES = "types";
|
||||
static const char * PAYLOAD = "payload";
|
||||
static const char * KEEPREDIRECTOR = "keepRedirector";
|
||||
static const char * NETWORK = "network";
|
||||
static const char * INTERFACE = "interface";
|
||||
static const char * BANDS = "bands";
|
||||
static const char * CHANNELS = "channels";
|
||||
static const char * VERBOSE = "verbose";
|
||||
static const char * MESSAGE = "message";
|
||||
static const char * STATE = "state";
|
||||
static const char * HEALTHCHECK = "healthcheck";
|
||||
static const char * PCAP_FILE_TYPE = "pcap";
|
||||
static const char * DURATION = "duration";
|
||||
static const char * NUMBEROFPACKETS = "numberOfPackets";
|
||||
static const char * FILTER = "filter";
|
||||
static const char * SELECT = "select";
|
||||
static const char * SERIALONLY = "serialOnly";
|
||||
static const char * COUNTONLY = "countOnly";
|
||||
static const char * DEVICEWITHSTATUS = "deviceWithStatus";
|
||||
static const char * DEVICESWITHSTATUS = "devicesWithStatus";
|
||||
static const char * DEVICES = "devices";
|
||||
static const char * COUNT = "count";
|
||||
static const char * SERIALNUMBERS = "serialNumbers";
|
||||
static const char * CONFIGURATIONS = "configurations";
|
||||
static const char * NAME = "name";
|
||||
static const char * COMMANDS = "commands";
|
||||
static const char * COMMANDUUID = "commandUUID";
|
||||
static const char * FIRMWARES = "firmwares";
|
||||
static const char * TOPIC = "topic";
|
||||
static const char * REASON = "reason";
|
||||
static const char * FILEUUID = "uuid";
|
||||
static const char * USERID = "userId";
|
||||
static const char * PASSWORD = "password";
|
||||
static const char * TOKEN = "token";
|
||||
static const char * SETLOGLEVEL = "setloglevel";
|
||||
static const char * GETLOGLEVEL = "getloglevel";
|
||||
static const char * PARAMETERS = "parameters";
|
||||
static const char * VALUE = "value";
|
||||
static const char * LASTONLY = "lastOnly";
|
||||
static const char * NEWEST = "newest";
|
||||
static const char * ACTIVESCAN = "activeScan";
|
||||
}
|
||||
|
||||
#endif // UCENTRALGW_RESTAPI_PROTOCOL_H
|
||||
89
src/RESTAPI_server.cpp
Normal file
89
src/RESTAPI_server.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "Poco/URI.h"
|
||||
|
||||
#include "RESTAPI_server.h"
|
||||
#include "RESTAPI_oauth2Handler.h"
|
||||
#include "RESTAPI_unknownRequestHandler.h"
|
||||
|
||||
#include "uUtils.h"
|
||||
|
||||
namespace uCentral::RESTAPI {
|
||||
|
||||
Service *Service::instance_ = nullptr;
|
||||
|
||||
int Start() {
|
||||
return Service::instance()->Start();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
Service::instance()->Stop();
|
||||
}
|
||||
|
||||
Service::Service() noexcept: SubSystemServer("RESTAPIServer", "RESTAPIServer", "ucentral.restapi")
|
||||
{
|
||||
}
|
||||
|
||||
int Service::Start() {
|
||||
Logger_.information("Starting.");
|
||||
|
||||
for(const auto & Svr: ConfigServersList_) {
|
||||
Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
|
||||
Svr.KeyFile(),Svr.CertFile()));
|
||||
|
||||
auto Sock{Svr.CreateSecureSocket(Logger_)};
|
||||
|
||||
// Sock.setReceiveTimeout(Poco::Timespan(10,0));
|
||||
|
||||
Svr.LogCert(Logger_);
|
||||
if(!Svr.RootCA().empty())
|
||||
Svr.LogCas(Logger_);
|
||||
|
||||
auto Params = new Poco::Net::HTTPServerParams;
|
||||
Params->setMaxThreads(50);
|
||||
Params->setMaxQueued(200);
|
||||
Params->setKeepAlive(true);
|
||||
// uint64_t T = 45000;
|
||||
// Params->setKeepAliveTimeout(T);
|
||||
// Params->setMaxKeepAliveRequests(200);
|
||||
// Params->setTimeout();
|
||||
|
||||
auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new RequestHandlerFactory, Pool_, Sock, Params);
|
||||
NewServer->start();
|
||||
RESTServers_.push_back(std::move(NewServer));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Poco::Net::HTTPRequestHandler *RequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
|
||||
|
||||
Logger_.debug(Poco::format("REQUEST(%s): %s %s", uCentral::Utils::FormatIPv6(Request.clientAddress().toString()), Request.getMethod(), Request.getURI()));
|
||||
|
||||
Poco::URI uri(Request.getURI());
|
||||
const auto & Path = uri.getPath();
|
||||
RESTAPIHandler::BindingMap bindings;
|
||||
|
||||
if (RESTAPIHandler::ParseBindings(Path, "/api/v1/oauth2/{token}", bindings)) {
|
||||
return new RESTAPI_oauth2Handler(bindings, Logger_);
|
||||
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/oauth2", bindings)) {
|
||||
return new RESTAPI_oauth2Handler(bindings, Logger_);
|
||||
}
|
||||
|
||||
Logger_.error(Poco::format("INVALID-API-ENDPOINT: %s",Path));
|
||||
return new RESTAPI_UnknownRequestHandler(bindings,Logger_);
|
||||
}
|
||||
|
||||
void Service::Stop() {
|
||||
Logger_.information("Stopping ");
|
||||
for( const auto & svr : RESTServers_ )
|
||||
svr->stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
63
src/RESTAPI_server.h
Normal file
63
src/RESTAPI_server.h
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_UCENTRALRESTAPISERVER_H
|
||||
#define UCENTRAL_UCENTRALRESTAPISERVER_H
|
||||
|
||||
#include "SubSystemServer.h"
|
||||
|
||||
#include "Poco/Net/HTTPServer.h"
|
||||
#include "Poco/Net/HTTPRequestHandler.h"
|
||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
|
||||
#include "Poco/Net/HTTPServerRequest.h"
|
||||
#include "Poco/Net/NetException.h"
|
||||
|
||||
|
||||
namespace uCentral::RESTAPI {
|
||||
int Start();
|
||||
void Stop();
|
||||
|
||||
class Service : public SubSystemServer {
|
||||
|
||||
public:
|
||||
Service() noexcept;
|
||||
|
||||
friend int Start();
|
||||
friend void Stop();
|
||||
|
||||
static Service *instance() {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new Service;
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
private:
|
||||
static Service *instance_;
|
||||
|
||||
int Start() override;
|
||||
void Stop() override;
|
||||
|
||||
std::vector<std::unique_ptr<Poco::Net::HTTPServer>> RESTServers_;
|
||||
Poco::ThreadPool Pool_;
|
||||
};
|
||||
|
||||
class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
|
||||
public:
|
||||
RequestHandlerFactory() :
|
||||
Logger_(Service::instance()->Logger()){}
|
||||
|
||||
Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
|
||||
private:
|
||||
Poco::Logger & Logger_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif //UCENTRAL_UCENTRALRESTAPISERVER_H
|
||||
16
src/RESTAPI_unknownRequestHandler.cpp
Normal file
16
src/RESTAPI_unknownRequestHandler.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "RESTAPI_unknownRequestHandler.h"
|
||||
|
||||
void RESTAPI_UnknownRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response)
|
||||
{
|
||||
if(!IsAuthorized(Request,Response))
|
||||
return;
|
||||
BadRequest(Request, Response);
|
||||
}
|
||||
25
src/RESTAPI_unknownRequestHandler.h
Normal file
25
src/RESTAPI_unknownRequestHandler.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H
|
||||
#define UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H
|
||||
|
||||
#include "RESTAPI_handler.h"
|
||||
|
||||
class RESTAPI_UnknownRequestHandler: public uCentral::RESTAPI::RESTAPIHandler
|
||||
{
|
||||
public:
|
||||
RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap & bindings,Poco::Logger & L)
|
||||
: RESTAPIHandler(bindings,L,
|
||||
std::vector<std::string>{}) {}
|
||||
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //UCENTRAL_RESTAPI_UNKNOWNREQUESTHANDLER_H
|
||||
265
src/SubSystemServer.cpp
Normal file
265
src/SubSystemServer.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#include "SubSystemServer.h"
|
||||
#include "Daemon.h"
|
||||
|
||||
#include "Poco/Net/X509Certificate.h"
|
||||
#include "Poco/DateTimeFormatter.h"
|
||||
#include "Poco/DateTimeFormat.h"
|
||||
|
||||
#include "openssl/ssl.h"
|
||||
|
||||
SubSystemServer::SubSystemServer( std::string Name,
|
||||
const std::string & LoggingPrefix,
|
||||
std::string SubSystemConfigPrefix )
|
||||
: Name_(std::move(Name)),
|
||||
Logger_(Poco::Logger::get(LoggingPrefix)),
|
||||
SubSystemConfigPrefix_(std::move(SubSystemConfigPrefix))
|
||||
{
|
||||
Logger_.setLevel(Poco::Message::PRIO_NOTICE);
|
||||
}
|
||||
|
||||
void SubSystemServer::initialize(Poco::Util::Application & self)
|
||||
{
|
||||
Logger_.notice("Initializing...");
|
||||
auto i=0;
|
||||
bool good=true;
|
||||
|
||||
while(good) {
|
||||
std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."};
|
||||
|
||||
std::string address{root + "address"};
|
||||
if(uCentral::ServiceConfig::GetString(address,"").empty()) {
|
||||
good = false;
|
||||
}
|
||||
else {
|
||||
std::string port{root + "port"};
|
||||
std::string key{root + "key"};
|
||||
std::string key_password{root + "key.password"};
|
||||
std::string cert{root + "cert"};
|
||||
std::string name{root + "name"};
|
||||
std::string backlog{root+"backlog"};
|
||||
std::string rootca{root+"rootca"};
|
||||
std::string issuer{root+"issuer"};
|
||||
std::string clientcas(root+"clientcas");
|
||||
std::string cas{root+"cas"};
|
||||
|
||||
std::string level{root+"security"};
|
||||
Poco::Net::Context::VerificationMode M=Poco::Net::Context::VERIFY_RELAXED;
|
||||
|
||||
auto L = uCentral::ServiceConfig::GetString(level,"");
|
||||
|
||||
if(L=="strict") {
|
||||
M=Poco::Net::Context::VERIFY_STRICT;
|
||||
} else if(L=="none") {
|
||||
M=Poco::Net::Context::VERIFY_NONE;
|
||||
} else if(L=="relaxed") {
|
||||
M=Poco::Net::Context::VERIFY_RELAXED;
|
||||
} else if(L=="once")
|
||||
M=Poco::Net::Context::VERIFY_ONCE;
|
||||
|
||||
PropertiesFileServerEntry entry( uCentral::ServiceConfig::GetString(address,""),
|
||||
uCentral::ServiceConfig::GetInt(port,0),
|
||||
uCentral::ServiceConfig::GetString(key,""),
|
||||
uCentral::ServiceConfig::GetString(cert,""),
|
||||
uCentral::ServiceConfig::GetString(rootca,""),
|
||||
uCentral::ServiceConfig::GetString(issuer,""),
|
||||
uCentral::ServiceConfig::GetString(clientcas,""),
|
||||
uCentral::ServiceConfig::GetString(cas,""),
|
||||
uCentral::ServiceConfig::GetString(key_password,""),
|
||||
uCentral::ServiceConfig::GetString(name,""),
|
||||
M,
|
||||
(int) uCentral::ServiceConfig::GetInt(backlog,64));
|
||||
ConfigServersList_.push_back(entry);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SubSystemServer::uninitialize()
|
||||
{
|
||||
}
|
||||
|
||||
void SubSystemServer::reinitialize(Poco::Util::Application & self)
|
||||
{
|
||||
// add your own reinitialization code here
|
||||
}
|
||||
|
||||
void SubSystemServer::defineOptions(Poco::Util::OptionSet& options)
|
||||
{
|
||||
}
|
||||
|
||||
Poco::Net::SecureServerSocket PropertiesFileServerEntry::CreateSecureSocket(Poco::Logger & L) const
|
||||
{
|
||||
Poco::Net::Context::Params P;
|
||||
|
||||
P.verificationMode = level_;
|
||||
P.verificationDepth = 9;
|
||||
P.loadDefaultCAs = root_ca_.empty();
|
||||
P.cipherList = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH";
|
||||
P.dhUse2048Bits = true;
|
||||
P.caLocation = cas_;
|
||||
|
||||
auto Context = new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P);
|
||||
|
||||
if(!cert_file_.empty() && !key_file_.empty()) {
|
||||
Poco::Crypto::X509Certificate Cert(cert_file_);
|
||||
Poco::Crypto::X509Certificate Root(root_ca_);
|
||||
|
||||
Context->useCertificate(Cert);
|
||||
Context->addChainCertificate(Root);
|
||||
|
||||
Context->addCertificateAuthority(Root);
|
||||
|
||||
if (level_ == Poco::Net::Context::VERIFY_STRICT) {
|
||||
if (issuer_cert_file_.empty()) {
|
||||
L.fatal("In strict mode, you must supply ans issuer certificate");
|
||||
}
|
||||
if(client_cas_.empty()) {
|
||||
L.fatal("In strict mode, client cas must be supplied");
|
||||
}
|
||||
Poco::Crypto::X509Certificate Issuing(issuer_cert_file_);
|
||||
Context->addChainCertificate(Issuing);
|
||||
Context->addCertificateAuthority(Issuing);
|
||||
}
|
||||
|
||||
Poco::Crypto::RSAKey Key("", key_file_, "");
|
||||
Context->usePrivateKey(Key);
|
||||
|
||||
SSL_CTX *SSLCtx = Context->sslContext();
|
||||
if (!SSL_CTX_check_private_key(SSLCtx)) {
|
||||
L.fatal(Poco::format("Wrong Certificate(%s) for Key(%s)", cert_file_, key_file_));
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(SSLCtx, SSL_VERIFY_PEER, nullptr);
|
||||
|
||||
if(level_==Poco::Net::Context::VERIFY_STRICT) {
|
||||
SSL_CTX_set_client_CA_list(SSLCtx, SSL_load_client_CA_file(client_cas_.c_str()));
|
||||
}
|
||||
SSL_CTX_enable_ct(SSLCtx, SSL_CT_VALIDATION_STRICT);
|
||||
SSL_CTX_dane_enable(SSLCtx);
|
||||
|
||||
Context->enableSessionCache();
|
||||
Context->setSessionCacheSize(0);
|
||||
Context->setSessionTimeout(10);
|
||||
Context->enableExtendedCertificateVerification(true);
|
||||
Context->disableStatelessSessionResumption();
|
||||
}
|
||||
|
||||
if (address_ == "*") {
|
||||
Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard(Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6 : Poco::Net::AddressFamily::IPv4 ));
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
|
||||
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||
}
|
||||
else {
|
||||
Poco::Net::IPAddress Addr(address_);
|
||||
Poco::Net::SocketAddress SockAddr(Addr, port_);
|
||||
|
||||
return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) {
|
||||
|
||||
L.information("=============================================================================================");
|
||||
L.information(Poco::format("> Issuer: %s",C.issuerName()));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(Poco::format("> Common Name: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||
L.information(Poco::format("> Country: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||
L.information(Poco::format("> Locality: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||
L.information(Poco::format("> State/Prov: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||
L.information(Poco::format("> Org name: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||
L.information(Poco::format("> Org unit: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||
L.information(Poco::format("> Email: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||
L.information(Poco::format("> Serial#: %s",C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(Poco::format("> Subject: %s",C.subjectName()));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(Poco::format("> Common Name: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME)));
|
||||
L.information(Poco::format("> Country: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY)));
|
||||
L.information(Poco::format("> Locality: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME)));
|
||||
L.information(Poco::format("> State/Prov: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE)));
|
||||
L.information(Poco::format("> Org name: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME)));
|
||||
L.information(Poco::format("> Org unit: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME)));
|
||||
L.information(Poco::format("> Email: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS)));
|
||||
L.information(Poco::format("> Serial#: %s",C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER)));
|
||||
L.information("---------------------------------------------------------------------------------------------");
|
||||
L.information(Poco::format("> Signature Algo: %s",C.signatureAlgorithm()));
|
||||
auto From = Poco::DateTimeFormatter::format(C.validFrom(),Poco::DateTimeFormat::HTTP_FORMAT);
|
||||
L.information(Poco::format("> Valid from: %s",From));
|
||||
auto Expires = Poco::DateTimeFormatter::format(C.expiresOn(),Poco::DateTimeFormat::HTTP_FORMAT);
|
||||
L.information(Poco::format("> Expires on: %s",Expires));
|
||||
L.information(Poco::format("> Version: %d",(int)C.version()));
|
||||
L.information(Poco::format("> Serial #: %s",C.serialNumber()));
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCert(Poco::Logger & L) const {
|
||||
try {
|
||||
Poco::Crypto::X509Certificate C(cert_file_);
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(Poco::format("Certificate Filename: %s",cert_file_));
|
||||
LogCertInfo(L,C);
|
||||
L.information("=============================================================================================");
|
||||
|
||||
if(!issuer_cert_file_.empty()) {
|
||||
Poco::Crypto::X509Certificate C1(issuer_cert_file_);
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(Poco::format("Issues Certificate Filename: %s",issuer_cert_file_));
|
||||
LogCertInfo(L,C1);
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
if(!client_cas_.empty()) {
|
||||
std::vector<Poco::Crypto::X509Certificate> Certs=Poco::Net::X509Certificate::readPEM(client_cas_);
|
||||
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(Poco::format("Client CAs Filename: %s",client_cas_));
|
||||
L.information("=============================================================================================");
|
||||
auto i=1;
|
||||
for(const auto & C3 : Certs)
|
||||
{
|
||||
L.information(Poco::format(" Index: %d",i));
|
||||
L.information("=============================================================================================");
|
||||
LogCertInfo(L, C3);
|
||||
i++;
|
||||
}
|
||||
L.information("=============================================================================================");
|
||||
}
|
||||
|
||||
} catch( const Poco::Exception & E) {
|
||||
L.log(E);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesFileServerEntry::LogCas(Poco::Logger & L) const {
|
||||
try {
|
||||
std::vector<Poco::Crypto::X509Certificate> Certs=Poco::Net::X509Certificate::readPEM(root_ca_);
|
||||
|
||||
L.information("=============================================================================================");
|
||||
L.information("=============================================================================================");
|
||||
L.information(Poco::format("CA Filename: %s",root_ca_));
|
||||
L.information("=============================================================================================");
|
||||
auto i=1;
|
||||
for(const auto & C : Certs)
|
||||
{
|
||||
L.information(Poco::format(" Index: %d",i));
|
||||
L.information("=============================================================================================");
|
||||
LogCertInfo(L, C);
|
||||
i++;
|
||||
}
|
||||
L.information("=============================================================================================");
|
||||
} catch ( const Poco::Exception & E ) {
|
||||
L.log(E);
|
||||
}
|
||||
}
|
||||
106
src/SubSystemServer.h
Normal file
106
src/SubSystemServer.h
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRAL_SUBSYSTEMSERVER_H
|
||||
#define UCENTRAL_SUBSYSTEMSERVER_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "Poco/Util/Application.h"
|
||||
#include "Poco/Util/Option.h"
|
||||
#include "Poco/Util/OptionSet.h"
|
||||
#include "Poco/Util/HelpFormatter.h"
|
||||
#include "Poco/Logger.h"
|
||||
#include "Poco/Net/SecureServerSocket.h"
|
||||
|
||||
#include "Poco/Net/X509Certificate.h"
|
||||
|
||||
using SubMutex = std::recursive_mutex;
|
||||
using SubMutexGuard = std::lock_guard<SubMutex>;
|
||||
|
||||
class PropertiesFileServerEntry {
|
||||
public:
|
||||
PropertiesFileServerEntry( std::string Address,
|
||||
uint32_t port,
|
||||
std::string Key_file,
|
||||
std::string Cert_file,
|
||||
std::string RootCa,
|
||||
std::string Issuer,
|
||||
std::string ClientCas,
|
||||
std::string Cas,
|
||||
std::string Key_file_password = "",
|
||||
std::string Name="",
|
||||
Poco::Net::Context::VerificationMode M=Poco::Net::Context::VerificationMode::VERIFY_RELAXED,
|
||||
int backlog=64) :
|
||||
address_(std::move(Address)),
|
||||
port_(port),
|
||||
key_file_(std::move(Key_file)),
|
||||
cert_file_(std::move(Cert_file)),
|
||||
root_ca_(std::move(RootCa)),
|
||||
issuer_cert_file_(std::move(Issuer)),
|
||||
client_cas_(std::move(ClientCas)),
|
||||
cas_(std::move(Cas)),
|
||||
key_file_password_(std::move(Key_file_password)),
|
||||
name_(std::move(Name)),
|
||||
level_(M),
|
||||
backlog_(backlog){};
|
||||
|
||||
[[nodiscard]] const std::string & Address() const { return address_; };
|
||||
[[nodiscard]] uint32_t Port() const { return port_; };
|
||||
[[nodiscard]] const std::string & KeyFile() const { return key_file_; };
|
||||
[[nodiscard]] const std::string & CertFile() const { return cert_file_; };
|
||||
[[nodiscard]] const std::string & RootCA() const { return root_ca_; };
|
||||
[[nodiscard]] const std::string & KeyFilePassword() const { return key_file_password_; };
|
||||
[[nodiscard]] const std::string & IssuerCertFile() const { return issuer_cert_file_; };
|
||||
[[nodiscard]] const std::string & Name() const { return name_; };
|
||||
[[nodiscard]] Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const;
|
||||
[[nodiscard]] int Backlog() const { return backlog_; }
|
||||
void LogCert( Poco::Logger & L ) const;
|
||||
void LogCas( Poco::Logger & L ) const;
|
||||
static void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C);
|
||||
|
||||
private:
|
||||
std::string address_;
|
||||
std::string cert_file_;
|
||||
std::string key_file_;
|
||||
std::string root_ca_;
|
||||
std::string key_file_password_;
|
||||
std::string issuer_cert_file_;
|
||||
std::string client_cas_;
|
||||
std::string cas_;
|
||||
uint32_t port_;
|
||||
std::string name_;
|
||||
int backlog_;
|
||||
Poco::Net::Context::VerificationMode level_;
|
||||
};
|
||||
|
||||
class SubSystemServer : public Poco::Util::Application::Subsystem {
|
||||
|
||||
public:
|
||||
SubSystemServer(std::string Name, const std::string & LoggingName, std::string SubSystemPrefix );
|
||||
void initialize(Poco::Util::Application &self) override;
|
||||
void uninitialize() override;
|
||||
void reinitialize(Poco::Util::Application & self) override;
|
||||
void defineOptions(Poco::Util::OptionSet &options) override;
|
||||
const char *name() const override { return Name_.c_str(); };
|
||||
|
||||
const PropertiesFileServerEntry & Host(int index) { return ConfigServersList_[index]; };
|
||||
Poco::Logger & Logger() { return Logger_;};
|
||||
void SetLoggingLevel(Poco::Message::Priority NewPriority) { Logger_.setLevel(NewPriority); }
|
||||
virtual int Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
|
||||
protected:
|
||||
SubMutex Mutex_{};
|
||||
Poco::Logger & Logger_;
|
||||
std::string Name_;
|
||||
std::vector<PropertiesFileServerEntry> ConfigServersList_;
|
||||
std::string SubSystemConfigPrefix_;
|
||||
};
|
||||
|
||||
#endif //UCENTRAL_SUBSYSTEMSERVER_H
|
||||
266
src/uUtils.cpp
Normal file
266
src/uUtils.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
#include <stdexcept>
|
||||
|
||||
#include "uUtils.h"
|
||||
|
||||
#include "Poco/Exception.h"
|
||||
#include "Poco/DateTimeFormat.h"
|
||||
#include "Poco/DateTimeFormatter.h"
|
||||
#include "Poco/DateTime.h"
|
||||
#include "Poco/DateTimeParser.h"
|
||||
#include "Poco/StringTokenizer.h"
|
||||
#include "uCentralProtocol.h"
|
||||
|
||||
namespace uCentral::Utils {
|
||||
|
||||
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial) {
|
||||
return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) &&
|
||||
std::all_of(Serial.begin(),Serial.end(),[](auto i){return std::isxdigit(i);}));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter ) {
|
||||
std::vector<std::string> ReturnList;
|
||||
|
||||
unsigned long P=0;
|
||||
|
||||
while(P<List.size())
|
||||
{
|
||||
unsigned long P2 = List.find_first_of(Delimiter, P);
|
||||
if(P2==std::string::npos) {
|
||||
ReturnList.push_back(List.substr(P));
|
||||
break;
|
||||
}
|
||||
else
|
||||
ReturnList.push_back(List.substr(P,P2-P));
|
||||
P=P2+1;
|
||||
}
|
||||
return ReturnList;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string FormatIPv6(const std::string & I )
|
||||
{
|
||||
if(I.substr(0,8) == "[::ffff:")
|
||||
{
|
||||
unsigned long PClosingBracket = I.find_first_of(']');
|
||||
|
||||
std::string ip = I.substr(8, PClosingBracket-8);
|
||||
std::string port = I.substr(PClosingBracket+1);
|
||||
return ip + port;
|
||||
}
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string SerialToMAC(const std::string &Serial) {
|
||||
std::string R = Serial;
|
||||
|
||||
if(R.size()<12)
|
||||
padTo(R,12,'0');
|
||||
else if (R.size()>12)
|
||||
R = R.substr(0,12);
|
||||
|
||||
char buf[18];
|
||||
|
||||
buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ;
|
||||
buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ;
|
||||
buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ;
|
||||
buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':';
|
||||
buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':';
|
||||
buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B) {
|
||||
std::string R;
|
||||
R.reserve(B.size()*2);
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
|
||||
for(const auto &i:B)
|
||||
{
|
||||
R += (hex[ (i & 0xf0) >> 4]);
|
||||
R += (hex[ (i & 0x0f) ]);
|
||||
}
|
||||
|
||||
return R;
|
||||
}
|
||||
|
||||
inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
inline static const char kPadCharacter = '=';
|
||||
|
||||
std::string base64encode(const byte *input, unsigned long size) {
|
||||
std::string encoded;
|
||||
encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
|
||||
|
||||
std::uint32_t temp;
|
||||
|
||||
std::size_t i;
|
||||
|
||||
int ee = (int)(size/3);
|
||||
|
||||
for (i = 0; i < 3*ee; ++i) {
|
||||
temp = input[i++] << 16;
|
||||
temp += input[i++] << 8;
|
||||
temp += input[i];
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]);
|
||||
}
|
||||
|
||||
switch (size % 3) {
|
||||
case 1:
|
||||
temp = input[i] << 16;
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(2, kPadCharacter);
|
||||
break;
|
||||
case 2:
|
||||
temp = input[i++] << 16;
|
||||
temp += input[i] << 8;
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]);
|
||||
encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]);
|
||||
encoded.append(1, kPadCharacter);
|
||||
break;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
std::vector<byte> base64decode(const std::string& input)
|
||||
{
|
||||
if(input.length() % 4)
|
||||
throw std::runtime_error("Invalid base64 length!");
|
||||
|
||||
std::size_t padding{};
|
||||
|
||||
if(input.length())
|
||||
{
|
||||
if(input[input.length() - 1] == kPadCharacter) padding++;
|
||||
if(input[input.length() - 2] == kPadCharacter) padding++;
|
||||
}
|
||||
|
||||
std::vector<byte> decoded;
|
||||
decoded.reserve(((input.length() / 4) * 3) - padding);
|
||||
|
||||
std::uint32_t temp{};
|
||||
auto it = input.begin();
|
||||
|
||||
while(it < input.end())
|
||||
{
|
||||
for(std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
temp <<= 6;
|
||||
if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41;
|
||||
else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47;
|
||||
else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04;
|
||||
else if(*it == 0x2B) temp |= 0x3E;
|
||||
else if(*it == 0x2F) temp |= 0x3F;
|
||||
else if(*it == kPadCharacter)
|
||||
{
|
||||
switch(input.end() - it)
|
||||
{
|
||||
case 1:
|
||||
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||
return decoded;
|
||||
case 2:
|
||||
decoded.push_back((temp >> 10) & 0x000000FF);
|
||||
return decoded;
|
||||
default:
|
||||
throw std::runtime_error("Invalid padding in base64!");
|
||||
}
|
||||
}
|
||||
else throw std::runtime_error("Invalid character in base64!");
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
decoded.push_back((temp >> 16) & 0x000000FF);
|
||||
decoded.push_back((temp >> 8 ) & 0x000000FF);
|
||||
decoded.push_back((temp ) & 0x000000FF);
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
std::string to_RFC3339(uint64_t t)
|
||||
{
|
||||
if(t==0)
|
||||
return "";
|
||||
return Poco::DateTimeFormatter::format(Poco::DateTime(Poco::Timestamp::fromEpochTime(t)), Poco::DateTimeFormat::ISO8601_FORMAT);
|
||||
}
|
||||
|
||||
uint64_t from_RFC3339(const std::string &TimeString)
|
||||
{
|
||||
if(TimeString.empty() || TimeString=="0")
|
||||
return 0;
|
||||
|
||||
try {
|
||||
int TZ;
|
||||
Poco::DateTime DT = Poco::DateTimeParser::parse(Poco::DateTimeFormat::ISO8601_FORMAT,TimeString,TZ);
|
||||
return DT.timestamp().epochTime();
|
||||
}
|
||||
catch( const Poco::Exception & E )
|
||||
{
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds) {
|
||||
Poco::StringTokenizer TimeTokens(Time,":",Poco::StringTokenizer::TOK_TRIM);
|
||||
|
||||
Hours = Minutes = Hours = 0 ;
|
||||
if(TimeTokens.count()==1) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
} else if(TimeTokens.count()==2) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||
} else if(TimeTokens.count()==3) {
|
||||
Hours = std::atoi(TimeTokens[0].c_str());
|
||||
Minutes = std::atoi(TimeTokens[1].c_str());
|
||||
Seconds = std::atoi(TimeTokens[2].c_str());
|
||||
} else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day) {
|
||||
Poco::StringTokenizer DateTokens(Time,"-",Poco::StringTokenizer::TOK_TRIM);
|
||||
|
||||
Year = Month = Day = 0 ;
|
||||
if(DateTokens.count()==3) {
|
||||
Year = std::atoi(DateTokens[0].c_str());
|
||||
Month = std::atoi(DateTokens[1].c_str());
|
||||
Day = std::atoi(DateTokens[2].c_str());
|
||||
} else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2) {
|
||||
if(H1<H2)
|
||||
return true;
|
||||
if(H1>H2)
|
||||
return false;
|
||||
if(M1<M2)
|
||||
return true;
|
||||
if(M2>M1)
|
||||
return false;
|
||||
if(S1<=S2)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
40
src/uUtils.h
Normal file
40
src/uUtils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// License type: BSD 3-Clause License
|
||||
// License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
|
||||
//
|
||||
// Created by Stephane Bourque on 2021-03-04.
|
||||
// Arilia Wireless Inc.
|
||||
//
|
||||
|
||||
#ifndef UCENTRALGW_UUTILS_H
|
||||
#define UCENTRALGW_UUTILS_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace uCentral::Utils {
|
||||
|
||||
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter=',');
|
||||
[[nodiscard]] std::string FormatIPv6(const std::string & I );
|
||||
inline void padTo(std::string& str, size_t num, char paddingChar = '\0') {
|
||||
str.append(num - str.length() % num, paddingChar);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string SerialToMAC(const std::string &Serial);
|
||||
[[nodiscard]] std::string ToHex(const std::vector<unsigned char> & B);
|
||||
|
||||
using byte = std::uint8_t;
|
||||
|
||||
[[nodiscard]] std::string base64encode(const byte *input, unsigned long size);
|
||||
std::vector<byte> base64decode(const std::string& input);
|
||||
|
||||
// [[nodiscard]] std::string to_RFC3339(uint64_t t);
|
||||
// [[nodiscard]] uint64_t from_RFC3339(const std::string &t);
|
||||
|
||||
bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds);
|
||||
bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day);
|
||||
bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2);
|
||||
|
||||
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial);
|
||||
}
|
||||
#endif // UCENTRALGW_UUTILS_H
|
||||
133
ucentralsec.properties
Normal file
133
ucentralsec.properties
Normal file
@@ -0,0 +1,133 @@
|
||||
#
|
||||
# uCentral protocol server for devices. This is where you point
|
||||
# all your devices. You can replace the * for address by the specific
|
||||
# address of one of your interfaces
|
||||
#
|
||||
|
||||
#
|
||||
# REST API access
|
||||
#
|
||||
ucentral.restapi.host.0.backlog = 100
|
||||
ucentral.restapi.host.0.security = relaxed
|
||||
ucentral.restapi.host.0.rootca = $UCENTRALSEC_ROOT/certs/restapi-ca.pem
|
||||
ucentral.restapi.host.0.address = *
|
||||
ucentral.restapi.host.0.port = 16001
|
||||
ucentral.restapi.host.0.cert = $UCENTRALSEC_ROOT/certs/restapi-cert.pem
|
||||
ucentral.restapi.host.0.key = $UCENTRALSEC_ROOT/certs/restapi-key.pem
|
||||
ucentral.restapi.host.0.key.password = mypassword
|
||||
|
||||
#
|
||||
# NLB Support
|
||||
#
|
||||
nlb.enable = true
|
||||
nlb.port = 15017
|
||||
|
||||
authentication.enabled = true
|
||||
authentication.default.username = tip@ucentral.com
|
||||
authentication.default.password = openwifi
|
||||
authentication.default.access = master
|
||||
authentication.service.type = internal
|
||||
firmware.autoupdate.policy.default = auto
|
||||
system.directory.data = $UCENTRALSEC_ROOT/data
|
||||
|
||||
ucentral.service.key = $UCENTRALSEC_ROOT/certs/restapi-key.pem
|
||||
ucentral.system.debug = true
|
||||
ucentral.system.uri = https://localhost:16001
|
||||
ucentral.system.id = 1
|
||||
ucentral.system.commandchannel = /tmp/app.ucentralgw
|
||||
|
||||
|
||||
#
|
||||
# Kafka
|
||||
#
|
||||
ucentral.kafka.enable = false
|
||||
ucentral.kafka.brokerlist = 127.0.0.1:9092
|
||||
ucentral.kafka.auto.commit = false
|
||||
ucentral.kafka.queue.buffering.max.ms = 50
|
||||
|
||||
#
|
||||
# This section select which form of persistence you need
|
||||
# Only one selected at a time. If you select multiple, this service will die if a horrible
|
||||
# death and might make your beer flat.
|
||||
#
|
||||
storage.type = sqlite
|
||||
#storage.type = postgresql
|
||||
#storage.type = mysql
|
||||
#storage.type = odbc
|
||||
|
||||
storage.type.sqlite.db = $UCENTRALSEC_ROOT/devices.db
|
||||
storage.type.sqlite.idletime = 120
|
||||
storage.type.sqlite.maxsessions = 128
|
||||
|
||||
storage.type.postgresql.maxsessions = 64
|
||||
storage.type.postgresql.idletime = 60
|
||||
storage.type.postgresql.host = localhost
|
||||
storage.type.postgresql.username = stephb
|
||||
storage.type.postgresql.password = snoopy99
|
||||
storage.type.postgresql.database = ucentral
|
||||
storage.type.postgresql.port = 5432
|
||||
storage.type.postgresql.connectiontimeout = 60
|
||||
|
||||
storage.type.mysql.maxsessions = 64
|
||||
storage.type.mysql.idletime = 60
|
||||
storage.type.mysql.host = localhost
|
||||
storage.type.mysql.username = stephb
|
||||
storage.type.mysql.password = snoopy99
|
||||
storage.type.mysql.database = ucentral
|
||||
storage.type.mysql.port = 3306
|
||||
storage.type.mysql.connectiontimeout = 60
|
||||
|
||||
#
|
||||
# Authentication
|
||||
#
|
||||
authentication.enabled = true
|
||||
authentication.default.username = tip@ucentral.com
|
||||
authentication.default.password = openwifi
|
||||
authentication.default.access = master
|
||||
authentication.service.type = internal
|
||||
|
||||
system.directory.data = $UCENTRALSEC_ROOT/data
|
||||
|
||||
ucentral.system.debug = true
|
||||
ucentral.system.uri = https://localhost:16001
|
||||
ucentral.system.id = 1
|
||||
ucentral.system.commandchannel = /tmp/app.ucentralgw
|
||||
|
||||
########################################################################
|
||||
########################################################################
|
||||
#
|
||||
# Thw following sections apply to the uCentral service
|
||||
#
|
||||
# Logging: please leave as is for now.
|
||||
#
|
||||
########################################################################
|
||||
########################################################################
|
||||
|
||||
logging.formatters.f1.class = PatternFormatter
|
||||
logging.formatters.f1.pattern = %s: [%p] %t
|
||||
logging.formatters.f1.times = UTC
|
||||
logging.channels.c1.class = ConsoleChannel
|
||||
logging.channels.c1.formatter = f1
|
||||
logging.channels.c2.class = FileChannel
|
||||
# This is where the logs will be written. This path MUST exist
|
||||
logging.channels.c2.path = $UCENTRALSEC_ROOT/logs/sample.log
|
||||
logging.channels.c2.formatter.class = PatternFormatter
|
||||
logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
|
||||
logging.channels.c3.class = ConsoleChannel
|
||||
logging.channels.c3.pattern = %s: [%p] %t
|
||||
# External Channel
|
||||
logging.loggers.root.channel = c2
|
||||
logging.loggers.root.level = debug
|
||||
# Inline Channel with PatternFormatter
|
||||
# logging.loggers.l1.name = logger1
|
||||
# logging.loggers.l1.channel.class = ConsoleChannel
|
||||
# logging.loggers.l1.channel.pattern = %s: [%p] %t
|
||||
# logging.loggers.l1.level = information
|
||||
# SplitterChannel
|
||||
# logging.channels.splitter.class = SplitterChannel
|
||||
# logging.channels.splitter.channels = l1,l2
|
||||
# logging.loggers.l2.name = logger2
|
||||
# logging.loggers.l2.channel = splitter
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user