This commit is contained in:
stephb9959
2021-06-22 22:31:03 -07:00
parent e8eed6a668
commit 89f423b605
24 changed files with 1544 additions and 386 deletions

View File

@@ -52,6 +52,7 @@ include_directories(/usr/local/include /usr/local/opt/openssl/include src inclu
add_executable( ucentralsec
build
src/Daemon.h src/Daemon.cpp
src/MicroService.cpp src/MicroService.h
src/SubSystemServer.cpp src/SubSystemServer.h
src/RESTAPI_unknownRequestHandler.h src/RESTAPI_unknownRequestHandler.cpp
src/RESTAPI_oauth2Handler.h src/RESTAPI_oauth2Handler.cpp
@@ -66,7 +67,10 @@ add_executable( ucentralsec
src/storage_identity.cpp
src/Utils.cpp src/Utils.h
src/storage_sqlite.cpp src/storage_odbc.cpp src/storage_sqlite.cpp src/storage_pgql.cpp src/storage_mysql.cpp
src/storage_tables.cpp)
src/storage_tables.cpp src/SMTPMailerService.cpp src/SMTPMailerService.h
src/RESTAPI_users_handler.cpp src/RESTAPI_users_handler.h
src/RESTAPI_user_handler.cpp src/RESTAPI_user_handler.h
src/RESTAPI_action_links.cpp src/RESTAPI_action_links.h)
target_link_libraries(ucentralsec PUBLIC
${Poco_LIBRARIES} ${Boost_LIBRARIES} ${ZLIB_LIBRARIES} ${AWSSDK_LINK_LIBRARIES} CppKafka::cppkafka )

View File

@@ -0,0 +1,36 @@
# User Validation Actions
The system uses email and links to offer user validation. Validation may include email verification, password reset
requests, etc.
## Action links
All action links will come back to the endpoint
```
https://site.com:port/api/v1/actions?command=payload
```
## Payload
The system encrypts the payload with its private key. A base64 encoder convert the encrypted message to something
valid for a URI.
```json
{
"id" : "uuid",
"email" : "email@host.com",
"type" : "verificationType"
}
```
- `id` : UUID of the request that should be outstanding
- `email` : email address of the user under verification
- `type` : could be one of "emailValidation", "passwordResetRequest", or other.
## Templates
Email templates come in 2 flavors: txt and html. The system replaces the following occurrences with system variables:
- {{action}} : the link that the user should press. That link should include the payload as above.
- {{name}} : the name of the user field.
- {{recipient}} : user email, asi-is

View File

@@ -0,0 +1,109 @@
# USER creation flow
## pre-requisite
To create a user in the system, someone must first login as the super user as configured in the properties file. Once logged in as super user,
thata person should create another user and convey the super user bit to them too. From that point on, only that second
created super user should be used. We will call superuser root0 and the created superuser root1
## About usernames
### email is your username
Your email address is your username. The username is case-insensitive.
### ASCII characters only
Usernames must only use ASCII characters.
### forcing domain names
You can allow only certain domain names by configuring the service with `email.includeonly` parameter
```asm
email.includeonly = mycorporatedomain.com
```
### excluding email domains
You may exclude e-mail domains you will not accept emails from in the configuration.
You could, for example, not allow people in gmail by adding
```asm
email.exclude = gmail.com
```
### precedence
If `email.includeonly` is used, `email.exclude` is ignored.
## Creating a username
In order to create a username, root1 must use the `/user/0` API call. The creation of a username involves:
- the service will email the new user to verify her email address
- the username remains dormant until the email verification completes
- the email verification maybe canceled anytime by deleting the username
- the email verification process times-out after `email.verification.timeout` in minutes
- the new user must change her password using the `/oauth2?changePassword=true` and filling in a `WebTokenRequestChangePassword` request
- the system will not accept any other calls until the user has changed her password
## Values accepted in user creation
The user creation request must provide the following in the `UserInfo` of the `post`.
```asm
id required = 0
name optional = a string for the user display
description: optional = a description of this user
avatar: optional = an avatar URI
email required = valid email address used as user name
validated: ignored
validationEmail: ignored
validationDate: ignored
created: ignored
valiadationURI: ignored
changePassword: ignored
lastLogin: ignored
currentLoginURI: ignored
lastPasswordChange: ignored
lastEmailCheck: ignored
currentPassword: ignored
lastPasswords: ignored
waitingForEmailCheck: ignored
notes: optional = cumulative notes that may be added in for this user
location: optionsl = UUID of a provisioning server location
owner: optional = UUID of a providioning server owner
suspended: optional = if true, the user can change password but not do anything else
blackListed: ignored
locale: optional = 2 letter code of country language, default to EN. If the language specified is not supported, EN is assumed.
userType: required = root/admin/csr/sub/system/special, defaults to sub
oauthType: optional = if using oauth, a recognized oauth provider
oauthUserInfo: ignored
```
## Values accepted during user update
When doing a `put`, these are the accepted fields.
```asm
id required = must match the ID in the path
name optional = a string for the user display
description: optional = a description of this user
avatar: optional = an avatar URI
email ignored
validated: ignored
validationEmail: ignored
validationDate: ignored
created: ignored
valiadationURI: ignored
changePassword: optonal = set to true to force a password change for the user
lastLogin: ignored
currentLoginURI: ignored
lastPasswordChange: ignored
lastEmailCheck: ignored
currentPassword: ignored
lastPasswords: ignored
waitingForEmailCheck: ignored
notes: optional = cumulative notes that may be added in for this user
location: optionsl = UUID of a provisioning server location
owner: optional = UUID of a providioning server owner
suspended: optional = if true, the user can change password but not do anything else
blackListed: optional = if true, user cannot login/deleted
locale: optional = 2 letter code of country language, default to EN. If the language specified is not supported, EN is assumed.
userType: required = root/admin/csr/sub/system/special, defaults to sub
oauthType: optional = if using oauth, a recognized oauth provider
oauthUserInfo: ignored
```

View File

@@ -2,7 +2,7 @@ openapi: 3.0.1
info:
title: uCentral Security API
description: A process to manage security logins
version: 0.0.1
version: 0.0.2
license:
name: BSD3
url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
@@ -64,7 +64,6 @@ components:
format: uuid
schemas:
GenericErrorResponse:
description: Typical error response
properties:
@@ -104,6 +103,28 @@ components:
userId: support@example.com
password: support
WebTokenRequestChangePassword:
description: User Id and password.
type: object
required:
- userId
- password
properties:
userId:
type: string
default: support@example.com
oldPassword:
type: string
default: support
newPassword:
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
@@ -125,6 +146,8 @@ components:
created:
type: integer
format: int64
userMustChangePassword:
type: boolean
aclTemplate:
$ref: '#/components/schemas/WebTokenAclTemplate'
@@ -203,6 +226,109 @@ components:
items:
$ref: '#/components/schemas/SystemEndpoint'
UserInfo:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
description:
type: string
avatar:
type: string
format: uri
email:
type: string
validated:
type: boolean
validationEmail:
type: string
validationDate:
type: integer
format: int64
created:
type: integer
format: int64
valiadationURI:
type: string
changePassword:
type: boolean
lastLogin:
type: integer
format: int64
currentLoginURI:
type: string
lastPasswordChange:
type: integer
format: int64
lastEmailCheck:
type: integer
format: int64
currentPassword:
type: string
lastPasswords:
type: array
items:
type: string
waitingForEmailCheck:
type: boolean
notes:
type: string
location:
type: string
format: uuid
owner:
type: string
format: uuid
suspended:
type: boolean
blackListed:
type: boolean
locale:
type: string
userRole:
type: string
enum:
- root
- admin
- sub
- csr
- system
- special
oauthType:
type: string
enum:
- internal
- normal
- gmail
- facebook
- linkedin
- instagram
oauthUserInfo:
type: string
UserList:
type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/UserInfo'
#########################################################################################
##
## These are endpoints that all services in the uCentral stack must provide
##
#########################################################################################
AnyPayload:
type: object
properties:
Document:
type: string
StringList:
type: object
properties:
@@ -249,6 +375,54 @@ components:
- $ref: '#/components/schemas/StringList'
- $ref: '#/components/schemas/TagValuePairList'
ProfileAction:
type: object
properties:
resource:
type: string
access:
type: string
enum:
- READ
- MODIFY
- DELETE
- CREATE
- TEST
- MOVE
SecurityProfile:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
description:
type: string
policy:
type: array
items:
$ref: '#/components/schemas/ProfileAction'
role:
type: string
notes:
type: string
SecurityProfileList:
type: object
properties:
profiles:
type: array
items:
$ref: '#/components/schemas/SecurityProfile'
#########################################################################################
##
## End of uCentral system wide values
##
#########################################################################################
paths:
/oauth2:
@@ -257,13 +431,21 @@ paths:
- Authentication
summary: Get access token - to be used as Bearer token header for all other API requests.
operationId: getAccessToken
parameters:
- in: query
name: changePassword
schema:
type: string
required: false
requestBody:
description: User id and password
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WebTokenRequest'
oneOf:
- $ref: '#/components/schemas/WebTokenRequestChangePassword'
- $ref: '#/components/schemas/WebTokenRequest'
responses:
200:
description: successful operation
@@ -319,6 +501,140 @@ paths:
404:
$ref: '#/components/responses/NotFound'
/users:
get:
tags:
- User Management
summary: Retrieve a list of existing users as well as some information about them.
operationId: getUsers
parameters:
- in: query
name: offset
schema:
type: integer
format: int64
required: false
- in: query
name: limit
schema:
type: integer
format: int64
required: false
- in: query
description: Selecting this option means the newest record will be returned. Use limit to select how many.
name: filter
schema:
type: string
required: false
responses:
200:
$ref: '#/components/schemas/UserList'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/user/{id}:
get:
tags:
- User Management
operationId: getUser
summary: Retrieve the information for a single user
parameters:
- in: path
name: id
schema:
type: integer
format: int64
required: true
responses:
200:
$ref: '#/components/schemas/UserInfo'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
delete:
tags:
- User Management
operationId: deleteUser
summary: Delete s single user
parameters:
- in: path
name: id
schema:
type: integer
format: int64
required: true
responses:
200:
$ref: '#/components/responses/Success'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
post:
tags:
- User Management
operationId: createUser
summary: Create a single user
parameters:
- in: path
name: id
#must be set to 0 for user creation
schema:
type: integer
format: int64
required: true
requestBody:
description: User details (some fields are ignored during creation)
content:
application/json:
schema:
$ref: '#/components/schemas/UserInfo'
responses:
200:
$ref: '#/components/schemas/UserInfo'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
put:
tags:
- User Management
operationId: updateUser
summary: Modifying a single user
parameters:
- in: path
name: id
schema:
type: integer
format: int64
required: true
requestBody:
description: User details (some fields are ignored during update)
content:
application/json:
schema:
$ref: '#/components/schemas/UserInfo'
responses:
200:
$ref: '#/components/schemas/UserInfo'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
#########################################################################################
##
## These are endpoints that all services in the uCentral stack must provide
##
#########################################################################################
/system:
post:
tags:
@@ -342,3 +658,91 @@ paths:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/callbackChannel:
post:
tags:
- Callback
summary: Generic callback hook
operationId: postCallback
parameters:
- in: query
name: subscribe
schema:
type: boolean
required: false
- in: query
name: uri
schema:
type: string
format: uri
- in: query
name: key
schema:
type: string
- in: query
name: topics
schema:
type: string
- in: query
name: id
schema:
type: string
- in: query
name: topic
schema:
type: string
requestBody:
description: A generic JSONDocument, may be empty too {}
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AnyPayload'
responses:
200:
$ref: '#/components/responses/Success'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/securityProfiles:
get:
tags:
- Security
summary: Retrieve the list of security profiles for a specific service type
operationId: getSecurituProfiles
parameters:
- in: query
description: Pagination start (starts at 1. If not specified, 1 is assumed)
name: offset
schema:
type: integer
required: false
- in: query
description: Maximum number of entries to return (if absent, no limit is assumed)
name: limit
schema:
type: integer
required: false
- in: query
description: Filter the results
name: filter
schema:
type: string
required: false
responses:
200:
$ref: '#/components/schemas/SecurityProfileList'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
#########################################################################################
##
## These are endpoints that all services in the uCentral stack must provide
##
#########################################################################################

View File

@@ -40,11 +40,6 @@ namespace uCentral {
return 1; // some compilers complain...
}
AuthService::AuthService() noexcept: SubSystemServer("Authentication", "AUTH-SVR", "authentication")
{
std::string E{"SHA512"};
}
int AuthService::Start() {
Signer_.setRSAKey(Daemon()->Key());
Signer_.addAllAlgorithms();
@@ -193,18 +188,17 @@ namespace uCentral {
if(Mechanism_=="internal")
{
if(((UserName == DefaultUserName_) && (Password == DefaultPassword_)) || !Secure_)
if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_)
{
ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
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());
auto PasswordHash = ComputePasswordHash(UserName, Password);
std::string TUser{UserName};
if(Storage()->GetIdentity(TUser,EncryptedPassword,USERNAME,ACL)) {
if(Storage()->GetIdentity(TUser,PasswordHash,USERNAME,ACL)) {
CreateToken(UserName, ResultToken, ACL);
return true;
}
@@ -212,4 +206,10 @@ namespace uCentral {
return false;
}
std::string AuthService::ComputePasswordHash(const std::string &UserName, const std::string &Password) {
std::string UName = Poco::trim(Poco::toLower(UserName));
SHA2_.update(Password + UName);
return uCentral::Utils::ToHex(SHA2_.digest());
}
} // end of namespace

View File

@@ -24,6 +24,7 @@ namespace uCentral{
class AuthService : public SubSystemServer {
public:
typedef std::map<std::string,uCentral::Objects::WebToken> WebTokenMap;
enum ACCESS_TYPE {
USERNAME,
SERVER,
@@ -48,18 +49,23 @@ namespace uCentral{
void Logout(const std::string &token);
[[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 );
[[nodiscard]] std::string ComputePasswordHash(const std::string &UserName, const std::string &Password);
[[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword);
[[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName);
private:
static AuthService *instance_;
std::map<std::string,uCentral::Objects::WebToken> Tokens_;
WebTokenMap Tokens_;
bool Secure_ = false ;
std::string DefaultUserName_;
std::string DefaultPassword_;
std::string Mechanism_;
bool AutoProvisioning_ = false ;
Poco::JWT::Signer Signer_;
Poco::SHA2Engine SHA2_;
AuthService() noexcept;
AuthService() noexcept:
SubSystemServer("Authentication", "AUTH-SVR", "authentication")
{
}
};
inline AuthService * AuthService() { return AuthService::instance(); }

View File

@@ -14,319 +14,46 @@
#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/File.h"
#include "Poco/Net/SSLManager.h"
#include "Utils.h"
#include "Daemon.h"
#include "ALBHealthCheckServer.h"
#include "KafkaManager.h"
#include "StorageService.h"
#include "RESTAPI_server.h"
#include "SMTPMailerService.h"
#include "Daemon.h"
namespace uCentral {
class Daemon *Daemon::instance_ = nullptr;
void MyErrorHandler::exception(const Poco::Exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
Daemon()->logger().log(E);
Daemon()->logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
}
void MyErrorHandler::exception(const std::exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
Daemon()->logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
}
void MyErrorHandler::exception() {
Poco::Thread * CurrentThread = Poco::Thread::current();
Daemon()->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();
SubSystems_ = Types::SubSystemVec{
Storage(),
RESTAPI_Server(),
KafkaManager(),
ALBHealthCheckServer()
};
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);
class Daemon *Daemon::instance() {
if (instance_ == nullptr) {
instance_ = new Daemon(vDAEMON_PROPERTIES_FILENAME,
vDAEMON_ROOT_ENV_VAR,
vDAEMON_CONFIG_ENV_VAR,
vDAEMON_APP_NAME,
Types::SubSystemVec{
Storage(),
RESTAPI_Server(),
KafkaManager(),
SMTPMailerService(),
ALBHealthCheckServer()
});
}
static const char * LogFilePathKey = "logging.channels.c2.path";
loadConfiguration(ConfigFile.toString());
if(LogDir_.empty()) {
std::string OriginalLogFileValue = ConfigPath(LogFilePathKey);
config().setString(LogFilePathKey, OriginalLogFileValue);
} else {
config().setString(LogFilePathKey, LogDir_);
}
Poco::File DataDir(ConfigPath("ucentral.system.data"));
DataDir_ = DataDir.path();
if(!DataDir.exists()) {
try {
DataDir.createDirectory();
} catch (const Poco::Exception &E) {
logger().log(E);
}
}
std::string KeyFile = ConfigPath("ucentral.service.key");
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, ""));
ID_ = ConfigGetInt("ucentral.system.id",1);
if(!DebugMode_)
DebugMode_ = ConfigGetBool("ucentral.system.debug",false);
InitializeSubSystemServers();
logger().information("Starting...");
ServerApplication::initialize(self);
return instance_;
}
void Daemon::uninitialize() {
// add your own uninitialization code here
ServerApplication::uninitialize();
void Daemon::initialize(Poco::Util::Application &self) {
MicroService::initialize(*this);
}
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);
}
void Daemon::InitializeSubSystemServers() {
for(auto i:SubSystems_)
addSubsystem(i);
}
void Daemon::StartSubSystemServers() {
for(auto i:SubSystems_)
i->Start();
}
void Daemon::StopSubSystemServers() {
for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i)
(*i)->Stop();
}
std::string Daemon::CreateUUID() {
return UUIDGenerator_.create().toString();
}
bool Daemon::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
try {
auto P = Poco::Logger::parseLevel(Level);
auto Sub = Poco::toLower(SubSystem);
if (Sub == "all") {
for (auto i : SubSystems_) {
i->Logger().setLevel(P);
}
return true;
} else {
std::cout << "Sub:" << SubSystem << " Level:" << Level << std::endl;
for (auto i : SubSystems_) {
if (Sub == Poco::toLower(i->Name())) {
i->Logger().setLevel(P);
return true;
}
}
}
} catch (const Poco::Exception & E) {
std::cout << "Exception" << std::endl;
}
return false;
}
Types::StringVec Daemon::GetSubSystems() const {
Types::StringVec Result;
for(auto i:SubSystems_)
Result.push_back(i->Name());
return Result;
}
Types::StringPairVec Daemon::GetLogLevels() const {
Types::StringPairVec Result;
for(auto &i:SubSystems_) {
auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
Result.push_back(P);
}
return Result;
}
const Types::StringVec & Daemon::GetLogLevelNames() const {
static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" };
return LevelNames;
}
uint64_t Daemon::ConfigGetInt(const std::string &Key,uint64_t Default) {
return (uint64_t) config().getInt64(Key,Default);
}
uint64_t Daemon::ConfigGetInt(const std::string &Key) {
return config().getInt(Key);
}
uint64_t Daemon::ConfigGetBool(const std::string &Key,bool Default) {
return config().getBool(Key,Default);
}
uint64_t Daemon::ConfigGetBool(const std::string &Key) {
return config().getBool(Key);
}
std::string Daemon::ConfigGetString(const std::string &Key,const std::string & Default) {
return config().getString(Key, Default);
}
std::string Daemon::ConfigGetString(const std::string &Key) {
return config().getString(Key);
}
std::string Daemon::ConfigPath(const std::string &Key,const std::string & Default) {
std::string R = config().getString(Key, Default);
return Poco::Path::expand(R);
}
std::string Daemon::ConfigPath(const std::string &Key) {
std::string R = config().getString(Key);
return Poco::Path::expand(R);
}
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.");
}
StartSubSystemServers();
instance()->waitForTerminationRequest();
StopSubSystemServers();
logger.notice(Poco::format("Stopped %s...",std::string(uCentral::DAEMON_APP_NAME)));
}
return Application::EXIT_OK;
}
}
int main(int argc, char **argv) {
try {
auto App = uCentral::Daemon::instance();
auto ExitCode = App->run(argc, argv);
delete App;
@@ -339,4 +66,5 @@ int main(int argc, char **argv) {
}
}
// end of namespace

View File

@@ -17,85 +17,36 @@
#include "Poco/UUIDGenerator.h"
#include "Poco/ErrorHandler.h"
#include "Poco/Crypto/RSAKey.h"
#include "Poco/Crypto/CipherFactory.h"
#include "Poco/Crypto/Cipher.h"
#include "SubSystemServer.h"
#include "uCentralTypes.h"
using Poco::Util::ServerApplication;
#include "MicroService.h"
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 {
static const char * vDAEMON_PROPERTIES_FILENAME = "ucentralsec.properties";
static const char * vDAEMON_ROOT_ENV_VAR = "UCENTRALSEC_ROOT";
static const char * vDAEMON_CONFIG_ENV_VAR = "UCENTRALSEC_CONFIG";
static const char * vDAEMON_APP_NAME = "uCentralSec";
class Daemon : public MicroService {
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();
static Daemon *instance() {
if (instance_ == nullptr) {
instance_ = new Daemon;
}
return instance_;
}
void InitializeSubSystemServers();
void StartSubSystemServers();
void StopSubSystemServers();
void Exit(int Reason);
bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
[[nodiscard]] static std::string Version();
[[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
[[nodiscard]] std::string CreateUUID();
[[nodiscard]] bool Debug() const { return DebugMode_; }
[[nodiscard]] uint64_t ID() const { return ID_; }
[[nodiscard]] Types::StringVec GetSubSystems() const;
[[nodiscard]] Types::StringPairVec GetLogLevels() const;
[[nodiscard]] const Types::StringVec & GetLogLevelNames() const;
[[nodiscard]] std::string ConfigGetString(const std::string &Key,const std::string & Default);
[[nodiscard]] std::string ConfigGetString(const std::string &Key);
[[nodiscard]] std::string ConfigPath(const std::string &Key,const std::string & Default);
[[nodiscard]] std::string ConfigPath(const std::string &Key);
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key,uint64_t Default);
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key);
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key,bool Default);
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key);
explicit Daemon(std::string PropFile,
std::string RootEnv,
std::string ConfigEnv,
std::string AppName,
Types::SubSystemVec SubSystems) :
MicroService( PropFile, RootEnv, ConfigEnv, AppName, SubSystems) {};
bool AutoProvisioning() const { return AutoProvisioning_ ; }
[[nodiscard]] std::string IdentifyDevice(const std::string & Compatible) const;
void initialize(Poco::Util::Application &self);
static Daemon *instance();
private:
static Daemon *instance_;
bool HelpRequested_ = false;
std::string LogDir_;
std::string ConfigFileName_;
Poco::UUIDGenerator UUIDGenerator_;
MyErrorHandler AppErrorHandler_;
uint64_t ID_ = 1;
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_ = nullptr;
bool DebugMode_ = false;
std::string DataDir_;
Types::SubSystemVec SubSystems_;
bool AutoProvisioning_ = false;
Types::StringMapStringSet DeviceTypeIdentifications_;
};
inline Daemon * Daemon() { return Daemon::instance(); }

319
src/MicroService.cpp Normal file
View File

@@ -0,0 +1,319 @@
//
// Created by stephane bourque on 2021-06-22.
//
#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/Net/HTTPSStreamFactory.h"
#include "Poco/Net/HTTPStreamFactory.h"
#include "Poco/Net/FTPSStreamFactory.h"
#include "Poco/Net/FTPStreamFactory.h"
#include "Poco/Path.h"
#include "Poco/File.h"
#include "Poco/String.h"
#include "MicroService.h"
#include "Utils.h"
namespace uCentral {
void MyErrorHandler::exception(const Poco::Exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
App_.logger().log(E);
App_.logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
}
void MyErrorHandler::exception(const std::exception & E) {
Poco::Thread * CurrentThread = Poco::Thread::current();
App_.logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
}
void MyErrorHandler::exception() {
Poco::Thread * CurrentThread = Poco::Thread::current();
App_.logger().warning(Poco::format("exception on %s",CurrentThread->getName()));
}
void MicroService::Exit(int Reason) {
std::exit(Reason);
}
void MicroService::initialize(Poco::Util::Application &self) {
Poco::Net::initializeSSL();
Poco::Net::HTTPStreamFactory::registerFactory();
Poco::Net::HTTPSStreamFactory::registerFactory();
Poco::Net::FTPStreamFactory::registerFactory();
Poco::Net::FTPSStreamFactory::registerFactory();
std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,".");
Poco::Path ConfigFile;
ConfigFile = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
if(!ConfigFile.isFile())
{
std::cerr << DAEMON_APP_NAME << ": Configuration "
<< ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR
+ " env variable the path of the " + 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 = ConfigPath(LogFilePathKey);
config().setString(LogFilePathKey, OriginalLogFileValue);
} else {
config().setString(LogFilePathKey, LogDir_);
}
Poco::File DataDir(ConfigPath("ucentral.system.data"));
DataDir_ = DataDir.path();
if(!DataDir.exists()) {
try {
DataDir.createDirectory();
} catch (const Poco::Exception &E) {
logger().log(E);
}
}
std::string KeyFile = ConfigPath("ucentral.service.key");
AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, ""));
Cipher_ = CipherFactory_.createCipher(*AppKey_);
ID_ = Utils::GetSystemId();
if(!DebugMode_)
DebugMode_ = ConfigGetBool("ucentral.system.debug",false);
InitializeSubSystemServers();
ServerApplication::initialize(self);
}
void MicroService::uninitialize() {
// add your own uninitialization code here
ServerApplication::uninitialize();
}
void MicroService::reinitialize(Poco::Util::Application &self) {
ServerApplication::reinitialize(self);
// add your own reinitialization code here
}
void MicroService::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<MicroService>(this, &MicroService::handleHelp)));
options.addOption(
Poco::Util::Option("file", "", "specify the configuration file")
.required(false)
.repeatable(false)
.argument("file")
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig)));
options.addOption(
Poco::Util::Option("debug", "", "to run in debug, set to true")
.required(false)
.repeatable(false)
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::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<MicroService>(this, &MicroService::handleLogs)));
options.addOption(
Poco::Util::Option("version", "", "get the version and quit.")
.required(false)
.repeatable(false)
.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion)));
}
std::string MicroService::Version() {
std::string V = APP_VERSION;
std::string B = BUILD_NUMBER;
return V + "(" + B + ")";
}
void MicroService::handleHelp(const std::string &name, const std::string &value) {
HelpRequested_ = true;
displayHelp();
stopOptionsProcessing();
}
void MicroService::handleVersion(const std::string &name, const std::string &value) {
HelpRequested_ = true;
std::cout << Version() << std::endl;
stopOptionsProcessing();
}
void MicroService::handleDebug(const std::string &name, const std::string &value) {
if(value == "true")
DebugMode_ = true ;
}
void MicroService::handleLogs(const std::string &name, const std::string &value) {
LogDir_ = value;
}
void MicroService::handleConfig(const std::string &name, const std::string &value) {
ConfigFileName_ = value;
}
void MicroService::displayHelp() {
Poco::Util::HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP.");
helpFormatter.format(std::cout);
}
void MicroService::InitializeSubSystemServers() {
for(auto i:SubSystems_)
addSubsystem(i);
}
void MicroService::StartSubSystemServers() {
for(auto i:SubSystems_)
i->Start();
}
void MicroService::StopSubSystemServers() {
for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i)
(*i)->Stop();
}
std::string MicroService::CreateUUID() {
return UUIDGenerator_.create().toString();
}
bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
try {
auto P = Poco::Logger::parseLevel(Level);
auto Sub = Poco::toLower(SubSystem);
if (Sub == "all") {
for (auto i : SubSystems_) {
i->Logger().setLevel(P);
}
return true;
} else {
std::cout << "Sub:" << SubSystem << " Level:" << Level << std::endl;
for (auto i : SubSystems_) {
if (Sub == Poco::toLower(i->Name())) {
i->Logger().setLevel(P);
return true;
}
}
}
} catch (const Poco::Exception & E) {
std::cout << "Exception" << std::endl;
}
return false;
}
Types::StringVec MicroService::GetSubSystems() const {
Types::StringVec Result;
for(auto i:SubSystems_)
Result.push_back(i->Name());
return Result;
}
Types::StringPairVec MicroService::GetLogLevels() const {
Types::StringPairVec Result;
for(auto &i:SubSystems_) {
auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
Result.push_back(P);
}
return Result;
}
const Types::StringVec & MicroService::GetLogLevelNames() const {
static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" };
return LevelNames;
}
uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) {
return (uint64_t) config().getInt64(Key,Default);
}
uint64_t MicroService::ConfigGetInt(const std::string &Key) {
return config().getInt(Key);
}
uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) {
return config().getBool(Key,Default);
}
uint64_t MicroService::ConfigGetBool(const std::string &Key) {
return config().getBool(Key);
}
std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) {
return config().getString(Key, Default);
}
std::string MicroService::ConfigGetString(const std::string &Key) {
return config().getString(Key);
}
std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) {
std::string R = config().getString(Key, Default);
return Poco::Path::expand(R);
}
std::string MicroService::ConfigPath(const std::string &Key) {
std::string R = config().getString(Key);
return Poco::Path::expand(R);
}
std::string MicroService::Encrypt(const std::string &S) {
return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
}
std::string MicroService::Decrypt(const std::string &S) {
return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
}
int MicroService::main(const ArgVec &args) {
MyErrorHandler ErrorHandler(*this);
Poco::ErrorHandler::set(&ErrorHandler);
if (!HelpRequested_) {
Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
logger.notice(Poco::format("Starting %s version %s.",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.");
}
logger.information(Poco::format("System ID set to %Lu",ID_));
StartSubSystemServers();
waitForTerminationRequest();
StopSubSystemServers();
logger.notice(Poco::format("Stopped %s...",DAEMON_APP_NAME));
}
return Application::EXIT_OK;
}
}

109
src/MicroService.h Normal file
View File

@@ -0,0 +1,109 @@
//
// Created by stephane bourque on 2021-06-22.
//
#ifndef UCENTRALGW_MICROSERVICE_H
#define UCENTRALGW_MICROSERVICE_H
#include <array>
#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"
#include "Poco/Crypto/CipherFactory.h"
#include "Poco/Crypto/Cipher.h"
#include "uCentralTypes.h"
#include "SubSystemServer.h"
namespace uCentral {
class MyErrorHandler : public Poco::ErrorHandler {
public:
explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {}
void exception(const Poco::Exception & E) override;
void exception(const std::exception & E) override;
void exception() override;
private:
Poco::Util::Application &App_;
};
class MicroService : public Poco::Util::ServerApplication {
public:
explicit MicroService( std::string PropFile,
std::string RootEnv,
std::string ConfigVar,
std::string AppName,
Types::SubSystemVec Subsystems) :
DAEMON_PROPERTIES_FILENAME(std::move(PropFile)),
DAEMON_ROOT_ENV_VAR(std::move(RootEnv)),
DAEMON_CONFIG_ENV_VAR(std::move(ConfigVar)),
DAEMON_APP_NAME(std::move(AppName)),
SubSystems_(Subsystems) {}
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();
void InitializeSubSystemServers();
void StartSubSystemServers();
void StopSubSystemServers();
void Exit(int Reason);
bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
[[nodiscard]] static std::string Version();
[[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
[[nodiscard]] std::string CreateUUID();
[[nodiscard]] bool Debug() const { return DebugMode_; }
[[nodiscard]] uint64_t ID() const { return ID_; }
[[nodiscard]] Types::StringVec GetSubSystems() const;
[[nodiscard]] Types::StringPairVec GetLogLevels() const;
[[nodiscard]] const Types::StringVec & GetLogLevelNames() const;
[[nodiscard]] std::string ConfigGetString(const std::string &Key,const std::string & Default);
[[nodiscard]] std::string ConfigGetString(const std::string &Key);
[[nodiscard]] std::string ConfigPath(const std::string &Key,const std::string & Default);
[[nodiscard]] std::string ConfigPath(const std::string &Key);
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key,uint64_t Default);
[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key);
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key,bool Default);
[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key);
[[nodiscard]] std::string Encrypt(const std::string &S);
[[nodiscard]] std::string Decrypt(const std::string &S);
private:
bool HelpRequested_ = false;
std::string LogDir_;
std::string ConfigFileName_;
Poco::UUIDGenerator UUIDGenerator_;
uint64_t ID_ = 1;
Poco::SharedPtr<Poco::Crypto::RSAKey> AppKey_ = nullptr;
bool DebugMode_ = false;
std::string DataDir_;
Types::SubSystemVec SubSystems_;
Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory();
Poco::Crypto::Cipher * Cipher_ = nullptr;
std::string DAEMON_PROPERTIES_FILENAME;
std::string DAEMON_ROOT_ENV_VAR;
std::string DAEMON_CONFIG_ENV_VAR;
std::string DAEMON_APP_NAME;
};
}
#endif // UCENTRALGW_MICROSERVICE_H

View File

@@ -0,0 +1,11 @@
//
// Created by stephane bourque on 2021-06-22.
//
#include "RESTAPI_action_links.h"
namespace uCentral {
void RESTAPI_action_links:: handleRequest(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) {
}
}

View File

@@ -0,0 +1,25 @@
//
// Created by stephane bourque on 2021-06-22.
//
#ifndef UCENTRALSEC_RESTAPI_ACTION_LINKS_H
#define UCENTRALSEC_RESTAPI_ACTION_LINKS_H
#include "RESTAPI_handler.h"
namespace uCentral {
class RESTAPI_action_links : public RESTAPIHandler {
public:
RESTAPI_action_links(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
: RESTAPIHandler(bindings, L,
std::vector<std::string>{
Poco::Net::HTTPRequest::HTTP_GET,
Poco::Net::HTTPRequest::HTTP_POST,
Poco::Net::HTTPRequest::HTTP_OPTIONS}) {}
void handleRequest(Poco::Net::HTTPServerRequest &Request,
Poco::Net::HTTPServerResponse &Response) override;
};
}
#endif //UCENTRALSEC_RESTAPI_ACTION_LINKS_H

View File

@@ -12,6 +12,9 @@
#include "RESTAPI_oauth2Handler.h"
#include "RESTAPI_unknownRequestHandler.h"
#include "RESTAPI_system_command.h"
#include "RESTAPI_user_handler.h"
#include "RESTAPI_users_handler.h"
#include "RESTAPI_action_links.h"
#include "Utils.h"
@@ -61,8 +64,14 @@ namespace uCentral {
return new RESTAPI_oauth2Handler(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/oauth2", bindings)) {
return new RESTAPI_oauth2Handler(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/users", bindings)) {
return new RESTAPI_users_handler(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/user", bindings)) {
return new RESTAPI_user_handler(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/system", bindings)) {
return new RESTAPI_system_command(bindings, Logger_);
} else if (RESTAPIHandler::ParseBindings(Path, "/api/v1/actions", bindings)) {
return new RESTAPI_action_links(bindings, Logger_);
}
Logger_.error(Poco::format("INVALID-API-ENDPOINT: %s",Path));

View File

@@ -0,0 +1,11 @@
//
// Created by stephane bourque on 2021-06-21.
//
#include "RESTAPI_user_handler.h"
namespace uCentral {
void RESTAPI_user_handler::handleRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
}
}

View File

@@ -0,0 +1,28 @@
//
// Created by stephane bourque on 2021-06-21.
//
#ifndef UCENTRALSEC_RESTAPI_USER_HANDLER_H
#define UCENTRALSEC_RESTAPI_USER_HANDLER_H
#include "RESTAPI_handler.h"
namespace uCentral {
class RESTAPI_user_handler : public RESTAPIHandler {
public:
RESTAPI_user_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
: RESTAPIHandler(bindings, L,
std::vector<std::string>
{Poco::Net::HTTPRequest::HTTP_POST,
Poco::Net::HTTPRequest::HTTP_GET,
Poco::Net::HTTPRequest::HTTP_PUT,
Poco::Net::HTTPRequest::HTTP_DELETE,
Poco::Net::HTTPRequest::HTTP_OPTIONS}) {}
void handleRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) override;
private:
};
}
#endif //UCENTRALSEC_RESTAPI_USER_HANDLER_H

View File

@@ -0,0 +1,11 @@
//
// Created by stephane bourque on 2021-06-21.
//
#include "RESTAPI_users_handler.h"
namespace uCentral {
void RESTAPI_users_handler::handleRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) {
}
}

View File

@@ -0,0 +1,25 @@
//
// Created by stephane bourque on 2021-06-21.
//
#ifndef UCENTRALSEC_RESTAPI_USERS_HANDLER_H
#define UCENTRALSEC_RESTAPI_USERS_HANDLER_H
#include "RESTAPI_handler.h"
namespace uCentral {
class RESTAPI_users_handler : public RESTAPIHandler {
public:
RESTAPI_users_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L)
: RESTAPIHandler(bindings, L,
std::vector<std::string>
{Poco::Net::HTTPRequest::HTTP_GET,
Poco::Net::HTTPRequest::HTTP_OPTIONS}) {}
void handleRequest(Poco::Net::HTTPServerRequest &Request, Poco::Net::HTTPServerResponse &Response) override;
private:
};
};
#endif //UCENTRALSEC_RESTAPI_USERS_HANDLER_H

94
src/SMTPMailerService.cpp Normal file
View File

@@ -0,0 +1,94 @@
//
// Created by stephane bourque on 2021-06-17.
//
#include <iostream>
#include "Poco/Net/MailMessage.h"
#include "Poco/Net/MailRecipient.h"
#include "Poco/Net/SMTPClientSession.h"
#include "Poco/Net/SecureSMTPClientSession.h"
#include "Poco/Net/StringPartSource.h"
#include "Poco/Path.h"
#include "Poco/Exception.h"
#include "Poco/Net/SSLManager.h"
#include "Poco/Net/Context.h"
#include "Poco/Net/InvalidCertificateHandler.h"
#include "Poco/Net/AcceptCertificateHandler.h"
#include "SMTPMailerService.h"
#include "Daemon.h"
namespace uCentral {
class SMTPMailerService * SMTPMailerService::instance_ = nullptr;
int SMTPMailerService::Start() {
MailHost_ = Daemon()->ConfigGetString("mailer.hostname");
SenderLoginUserName_ = Daemon()->ConfigGetString("mailer.username");
SenderLoginPassword_ = Daemon()->ConfigGetString("mailer.password");
LoginMethod_ = Daemon()->ConfigGetString("mailer.loginmethod");
MailHostPort_ = Daemon()->ConfigGetInt("mailer.port");
return 0;
}
void SMTPMailerService::Stop() {
}
bool SMTPMailerService::SendMessage(SMTPMailerService::MessageAttributes Attrs) {
return false;
}
bool SMTPMailerService::SendIt() {
try
{
Poco::SharedPtr<Poco::Net::InvalidCertificateHandler> ptrHandler = new Poco::Net::AcceptCertificateHandler(false);
/*
Poco::Net::MailMessage message;
message.setSender(Sender_);
message.addRecipient(MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, recipient));
message.setSubject("Hello from the POCO C++ Libraries");
std::string content;
content += "Hello ";
content += recipient;
content += ",\r\n\r\n";
content += "This is a greeting from the POCO C++ Libraries.\r\n\r\n";
std::string logo(reinterpret_cast<const char*>(PocoLogo), sizeof(PocoLogo));
message.addContent(new Poco::Net::StringPartSource(content));
message.addAttachment("logo", new Poco::Net::StringPartSource(logo, "image/gif"));
Poco::Net::SecureSMTPClientSession session(MailHost_,MailHostPort_);
Poco::Net::Context::Params P;
auto ptrContext = new Poco::Net::Context( Poco::Net::Context::CLIENT_USE, "", "", "",
Poco::Net::Context::VERIFY_RELAXED, 9, true,
"ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
Poco::Net::SSLManager::instance().initializeClient(nullptr,
ptrHandler,
ptrContext);
session.login();
session.startTLS(ptrContext);
session.login(MailHost_,
Poco::Net::SecureSMTPClientSession::AUTH_LOGIN,
SenderLoginUserName_,
SenderLoginPassword_
);
session.sendMessage(message);
session.close();
*/
}
catch (const Poco::Exception& exc)
{
std::cerr << exc.displayText() << std::endl;
return 1;
}
return 0;
}
}

60
src/SMTPMailerService.h Normal file
View File

@@ -0,0 +1,60 @@
//
// Created by stephane bourque on 2021-06-17.
//
#ifndef UCENTRALSEC_SMTPMAILERSERVICE_H
#define UCENTRALSEC_SMTPMAILERSERVICE_H
#include "SubSystemServer.h"
namespace uCentral {
class SMTPMailerService : public SubSystemServer {
public:
enum MESSAGE_ATTRIBUTES {
RECIPIENT_EMAIL,
RECIPIENT_FIRST_NAME,
RECIPIENT_LAST_NAME,
RECIPIENT_INITIALS,
RECIPIENT_FULL_NAME,
RECIPIENT_SALUTATION,
SUBJECT,
SIGNATURE,
TEMPLATE,
LOGO
};
typedef std::map<MESSAGE_ATTRIBUTES, std::string> MessageAttributes;
static SMTPMailerService *instance() {
if (instance_ == nullptr) {
instance_ = new SMTPMailerService;
}
return instance_;
}
int Start() override;
void Stop() override;
bool SendMessage(MessageAttributes Attrs);
bool SendIt();
private:
static SMTPMailerService * instance_;
std::string MailHost_;
std::string Sender_;
int MailHostPort_=25;
std::string SenderLoginUserName_;
std::string SenderLoginPassword_;
std::string LoginMethod_ = "login";
std::string LogoFileName_;
SMTPMailerService() noexcept:
SubSystemServer("SMTPMailer", "MAILER-SVR", "smtpmailer")
{
std::string E{"SHA512"};
}
};
inline SMTPMailerService * SMTPMailerService() { return SMTPMailerService::instance(); }
}
#endif //UCENTRALSEC_SMTPMAILERSERVICE_H

View File

@@ -36,10 +36,13 @@ namespace uCentral {
odbc
};
enum CommandExecutionType {
COMMAND_PENDING,
COMMAND_EXECUTED,
COMMAND_COMPLETED
enum AUTH_ERROR {
SUCCESS,
PASSWORD_CHANGE_REQUIRED,
PASSWORD_DOES_NOT_MATCH,
PASSWORD_ALREADY_USED,
USERNAME_PENDING_VERIFICATION,
PASSWORD_INVALID
};
static Storage *instance() {
@@ -52,6 +55,10 @@ namespace uCentral {
int Start() override;
void Stop() override;
int CreateUser(const std::string & Admin, const std::string &UserName, const std::string &Password);
bool DeleteUser(const std::string & Admin, const std::string &UserName);
bool ChangePassword(const std::string & Admin, const std::string &UserName, const std::string &OldPassword, const std::string &NewPassword);
bool IdentityExists(std::string & Identity, AuthService::ACCESS_TYPE Type);
bool AddIdentity(std::string & Identity, std::string & Password, AuthService::ACCESS_TYPE Type, uCentral::Objects::AclTemplate & ACL);
bool GetIdentity(std::string & Identity, std::string & Password,AuthService::ACCESS_TYPE Type, uCentral::Objects::AclTemplate & ACL);
@@ -86,6 +93,8 @@ namespace uCentral {
#endif
int Create_Tables();
int Create_UserTable();
int Create_APIKeyTable();
int Setup_SQLite();
[[nodiscard]] std::string ConvertParams(const std::string &S) const;

View File

@@ -6,6 +6,8 @@
// Arilia Wireless Inc.
//
#include <stdexcept>
#include <fstream>
#include <cstdlib>
#include "Utils.h"
@@ -17,8 +19,10 @@
#include "Poco/StringTokenizer.h"
#include "Poco/Logger.h"
#include "Poco/Message.h"
#include "Poco/File.h"
#include "uCentralProtocol.h"
#include "Daemon.h"
namespace uCentral::Utils {
@@ -279,4 +283,110 @@ namespace uCentral::Utils {
}
}
bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits) {
auto S1_i = SerialNumberToInt(S1);
auto S2_i = SerialNumberToInt(S2);
return ((S1_i>>Bits)==(S2_i>>Bits));
}
uint64_t SerialNumberToInt(const std::string & S) {
uint64_t R=0;
for(const auto &i:S)
if(i>='0' && i<='9') {
R <<= 4;
R += (i-'0');
} else if(i>='a' && i<='f') {
R <<= 4;
R += (i-'a') + 10 ;
} else if(i>='A' && i<='F') {
R <<= 4;
R += (i-'A') + 10 ;
}
return R;
}
uint64_t SerialNumberToOUI(const std::string & S) {
uint64_t Result = 0 ;
int Digits=0;
for(const auto &i:S) {
if(std::isxdigit(i)) {
if(i>='0' && i<='9') {
Result <<=4;
Result += i-'0';
} else if(i>='A' && i<='F') {
Result <<=4;
Result += i-'A'+10;
} else if(i>='a' && i<='f') {
Result <<=4;
Result += i-'a'+10;
}
Digits++;
if(Digits==6)
break;
}
}
return Result;
}
uint64_t GetDefaultMacAsInt64() {
uint64_t Result=0;
auto IFaceList = Poco::Net::NetworkInterface::list();
for(const auto &iface:IFaceList) {
if(iface.isRunning() && !iface.isLoopback()) {
auto MAC = iface.macAddress();
for (auto const &i : MAC) {
Result <<= 8;
Result += (uint8_t)i;
}
if (Result != 0)
break;
}
}
return Result;
}
void SaveSystemId(uint64_t Id) {
try {
std::ofstream O;
O.open(Daemon()->DataDir() + "/system.id",std::ios::binary | std::ios::trunc);
O << Id;
O.close();
} catch (...)
{
std::cout << "Could not save system ID" << std::endl;
}
}
uint64_t InitializeSystemId() {
uint64_t R = ~ std::rand();
auto S = GetDefaultMacAsInt64() ^ R;
SaveSystemId(S);
return S;
}
uint64_t GetSystemId() {
uint64_t ID=0;
// if the system ID file exists, open and read it.
Poco::File SID( Daemon()->DataDir() + "/system.id");
try {
if (SID.exists()) {
std::ifstream I;
I.open(SID.path());
I >> ID;
I.close();
if (ID == 0)
return InitializeSystemId();
return ID;
} else {
return InitializeSystemId();
}
} catch (...) {
return InitializeSystemId();
}
}
}

View File

@@ -12,6 +12,8 @@
#include <vector>
#include <string>
#include "Poco/Net/NetworkInterface.h"
namespace uCentral::Utils {
[[nodiscard]] std::vector<std::string> Split(const std::string &List, char Delimiter=',');
@@ -37,5 +39,12 @@ namespace uCentral::Utils {
[[nodiscard]] bool ValidSerialNumber(const std::string &Serial);
[[nodiscard]] std::string LogLevelToString(int Level);
[[nodiscard]] bool SerialNumberMatch(const std::string &S1, const std::string &S2, int extrabits=2);
[[nodiscard]] uint64_t SerialNumberToInt(const std::string & S);
[[nodiscard]] uint64_t SerialNumberToOUI(const std::string & S);
[[nodiscard]] uint64_t GetDefaultMacAsInt64();
[[nodiscard]] uint64_t GetSystemId();
}
#endif // UCENTRALGW_UTILS_H

View File

@@ -9,4 +9,85 @@ namespace uCentral {
int Storage::Create_Tables() {
return 0;
}
int Storage::Create_UserTable() {
Poco::Data::Session Sess = Pool_->get();
try {
if (dbType_ == mysql) {
Sess << "CREATE TABLE IF NOT EXISTS Users ("
"Id Id unique primary key, "
"name varchar, "
"description varchar, "
"avatar varchar, "
"email varchar, "
"validated int, "
"validationEmail varchar, "
"validationDate bigint, "
"creationDate bigint, "
"validationURI text, "
"changePassword int, "
"lastLogin bigint, "
"currentLoginURI varchar, "
"lastPasswordChange bigint, "
"lastEmailCheck bigint, "
"currentPassword varchar, "
"lastPasswords varchar,"
"waitingForEmailCheck int, "
"locale varchar, "
"notes text, "
"location text, "
"owner varchar, "
"suspended int, "
"blackListed int, "
"userType varchar, "
"userTypeProprietaryInfo text"
" ,INDEX emailindex (email ASC)"
" ,INDEX nameindex (name ASC))",
Poco::Data::Keywords::now;
} else {
Sess << "CREATE TABLE IF NOT EXISTS Users ("
"Id Id unique primary key, "
"name varchar, "
"description varchar, "
"avatar varchar, "
"email varchar, "
"validated int, "
"validationEmail varchar, "
"validationDate bigint, "
"creationDate bigint, "
"validationURI text, "
"changePassword int, "
"lastLogin bigint, "
"currentLoginURI varchar, "
"lastPasswordChange bigint, "
"lastEmailCheck bigint, "
"currentPassword varchar, "
"lastPasswords varchar,"
"waitingForEmailCheck int, "
"locale varchar, "
"notes text, "
"location text, "
"owner varchar, "
"suspended int, "
"blackListed int, "
"userType varchar, "
"userTypeProprietaryInfo text"
")",
Poco::Data::Keywords::now;
Sess << "CREATE INDEX IF NOT EXISTS emailindex ON Users (email ASC)", Poco::Data::Keywords::now;
Sess << "CREATE INDEX IF NOT EXISTS nameindex ON Users (name ASC)", Poco::Data::Keywords::now;
}
return 0;
} catch (const Poco::Exception &E) {
Logger_.log(E);
}
return 1;
}
int Storage::Create_APIKeyTable() {
return 0;
}
}

View File

@@ -35,7 +35,12 @@ ucentral.system.debug = true
ucentral.system.uri = https://localhost:16001
ucentral.system.id = 1
ucentral.system.commandchannel = /tmp/app.ucentralgw
mailer.hostname = smtp.gmail.com
mailer.username = no-reply@arilia.com
mailer.password = pink-elephants-play-hockey
mailer.loginmethod = login
mailer.port = 587
#
# Kafka
@@ -82,7 +87,7 @@ storage.type.mysql.connectiontimeout = 60
#
authentication.enabled = true
authentication.default.username = tip@ucentral.com
authentication.default.password = openwifi
authentication.default.password = 13268b7daa751240369d125e79c873bd8dd3bef7981bdfd38ea03dbb1fbe7dcf
authentication.default.access = master
authentication.service.type = internal
@@ -90,9 +95,13 @@ 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
# email.includeonly = mydomain.com
# email.exclude = gmail.com
########################################################################
########################################################################
#