From 6e72c28b3e9cbfdff48c97dfe78b226c1cd7337a Mon Sep 17 00:00:00 2001 From: stephb9959 Date: Mon, 25 Apr 2022 22:55:37 -0700 Subject: [PATCH] Adding refresh token processing. --- build | 2 +- openpapi/owsec.yaml | 1 + src/AuthService.cpp | 79 ++++++++++++++++++++- src/AuthService.h | 6 +- src/RESTAPI/RESTAPI_oauth2_handler.cpp | 13 ++++ src/RESTObjects/RESTAPI_SecurityObjects.cpp | 5 ++ src/RESTObjects/RESTAPI_SecurityObjects.h | 2 + src/framework/MicroService.h | 6 +- src/storage/orm_tokens.cpp | 33 ++++++++- src/storage/orm_tokens.h | 8 ++- 10 files changed, 147 insertions(+), 8 deletions(-) diff --git a/build b/build index ac4213d..7d37386 100644 --- a/build +++ b/build @@ -1 +1 @@ -43 \ No newline at end of file +45 \ No newline at end of file diff --git a/openpapi/owsec.yaml b/openpapi/owsec.yaml index 769556d..06f18b2 100644 --- a/openpapi/owsec.yaml +++ b/openpapi/owsec.yaml @@ -66,6 +66,7 @@ components: - 11 # BAD_MFA_TRANSACTION - 12 # MFA_FAILURE - 13 # SECURITY_SERVICE_UNREACHABLE + - 14 # CANNOT REFRESH TOKEN ErrorDetails: type: string ErrorDescription: diff --git a/src/AuthService.cpp b/src/AuthService.cpp index 7990658..bf19188 100644 --- a/src/AuthService.cpp +++ b/src/AuthService.cpp @@ -45,6 +45,7 @@ namespace OpenWifi { int AuthService::Start() { Logger().notice("Starting..."); TokenAging_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60); + RefreshTokenLifeSpan_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.refresh_token.lifespan", 90 * 24 * 60 * 600); HowManyOldPassword_ = MicroService::instance().ConfigGetInt("authentication.oldpasswords", 5); AccessPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.access", "/wwwassets/access_policy.html"); @@ -62,7 +63,83 @@ namespace OpenWifi { Logger().notice("Stopping..."); } - bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) + bool AuthService::RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) { + try { + std::string CallToken; + Poco::Net::OAuth20Credentials Auth(Request); + if (Auth.getScheme() == "Bearer") { + CallToken = Auth.getBearerToken(); + } + + if (CallToken.empty()) { + return false; + } + + uint64_t RevocationDate=0; + std::string UserId; + if(StorageService()->UserTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) { + auto now = OpenWifi::Now(); + + // Create a new token + auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM); + auto NewRefreshToken = RefreshToken; + if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) { + NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM); + UI.webtoken.lastRefresh_ = now; + } + + StorageService()->UserTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ ); + UI.webtoken.access_token_ = NewToken; + UI.webtoken.refresh_token_ = NewRefreshToken; + return true; + } + return false; + + } catch (...) { + + } + return false; + } + + bool AuthService::RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) { + try { + std::string CallToken; + Poco::Net::OAuth20Credentials Auth(Request); + if (Auth.getScheme() == "Bearer") { + CallToken = Auth.getBearerToken(); + } + + if (CallToken.empty()) { + return false; + } + + uint64_t RevocationDate=0; + std::string UserId; + if(StorageService()->SubTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) { + auto now = OpenWifi::Now(); + + // Create a new token + auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM); + auto NewRefreshToken = RefreshToken; + if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) { + NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM); + UI.webtoken.lastRefresh_ = now; + } + + StorageService()->SubTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ ); + UI.webtoken.access_token_ = NewToken; + UI.webtoken.refresh_token_ = NewRefreshToken; + return true; + } + return false; + + } catch (...) { + + } + return false; + } + + bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) { std::lock_guard Guard(Mutex_); Expired = false; diff --git a/src/AuthService.h b/src/AuthService.h index 88cf326..ea05b1a 100644 --- a/src/AuthService.h +++ b/src/AuthService.h @@ -113,6 +113,9 @@ namespace OpenWifi{ inline const std::string & GetSubPasswordPolicy() const { return SubPasswordPolicy_; } inline const std::string & GetSubAccessPolicy() const { return SubAccessPolicy_; } + bool RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI); + bool RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI); + private: Poco::SHA2Engine SHA2_; @@ -125,8 +128,9 @@ namespace OpenWifi{ std::regex PasswordValidation_; std::regex SubPasswordValidation_; - uint64_t TokenAging_ = 30 * 24 * 60 * 60; + uint64_t TokenAging_ = 15 * 24 * 60 * 60; uint64_t HowManyOldPassword_=5; + uint64_t RefreshTokenLifeSpan_ = 90 * 24 * 60 * 60 ; class SHA256Engine : public Poco::Crypto::DigestEngine { diff --git a/src/RESTAPI/RESTAPI_oauth2_handler.cpp b/src/RESTAPI/RESTAPI_oauth2_handler.cpp index 698484d..17cc382 100644 --- a/src/RESTAPI/RESTAPI_oauth2_handler.cpp +++ b/src/RESTAPI/RESTAPI_oauth2_handler.cpp @@ -60,9 +60,22 @@ namespace OpenWifi { auto userId = GetS(RESTAPI::Protocol::USERID, Obj); auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj); + auto refreshToken = GetS("refresh_token", Obj); + auto grant_type = GetParameter("grant_type"); Poco::toLowerInPlace(userId); + if(!refreshToken.empty() && grant_type == "refresh_token") { + SecurityObjects::UserInfoAndPolicy UInfo; + if(AuthService()->RefreshUserToken(*Request, refreshToken, UInfo)) { + Poco::JSON::Object Answer; + UInfo.webtoken.to_json(Answer); + return ReturnObject(Answer); + } else { + return UnAuthorized(RESTAPI::Errors::InvalidCredentials, CANNOT_REFRESH_TOKEN); + } + } + if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS, false)) { Logger_.information(fmt::format("POLICY-REQUEST({}): Request.", Request->clientAddress().toString())); Poco::JSON::Object Answer; diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.cpp b/src/RESTObjects/RESTAPI_SecurityObjects.cpp index cab581f..7964b2d 100644 --- a/src/RESTObjects/RESTAPI_SecurityObjects.cpp +++ b/src/RESTObjects/RESTAPI_SecurityObjects.cpp @@ -113,6 +113,8 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"userMustChangePassword",userMustChangePassword); field_to_json(Obj,"errorCode", errorCode); Obj.set("aclTemplate",AclTemplateObj); + field_to_json(Obj,"errorCode", errorCode); + field_to_json(Obj,"lastRefresh", lastRefresh_); } bool WebToken::from_json(const Poco::JSON::Object::Ptr &Obj) { @@ -129,6 +131,7 @@ namespace OpenWifi::SecurityObjects { field_from_json(Obj, "created", created_); field_from_json(Obj, "username", username_); field_from_json(Obj, "userMustChangePassword",userMustChangePassword); + field_from_json(Obj,"lastRefresh", lastRefresh_); return true; } catch (...) { std::cout << "Cannot parse: WebToken" << std::endl; @@ -588,6 +591,7 @@ namespace OpenWifi::SecurityObjects { field_to_json(Obj,"expires",expires); field_to_json(Obj,"idleTimeout",idleTimeout); field_to_json(Obj,"revocationDate",revocationDate); + field_to_json(Obj,"lastRefresh", lastRefresh); } bool Token::from_json(const Poco::JSON::Object::Ptr &Obj) { @@ -600,6 +604,7 @@ namespace OpenWifi::SecurityObjects { field_from_json(Obj,"expires",expires); field_from_json(Obj,"idleTimeout",idleTimeout); field_from_json(Obj,"revocationDate",revocationDate); + field_from_json(Obj,"lastRefresh", lastRefresh); return true; } catch(...) { std::cout << "Cannot parse: Token" << std::endl; diff --git a/src/RESTObjects/RESTAPI_SecurityObjects.h b/src/RESTObjects/RESTAPI_SecurityObjects.h index 1cc0df6..c4bfc1f 100644 --- a/src/RESTObjects/RESTAPI_SecurityObjects.h +++ b/src/RESTObjects/RESTAPI_SecurityObjects.h @@ -41,6 +41,7 @@ namespace OpenWifi { uint64_t idle_timeout_=0; AclTemplate acl_template_; uint64_t created_=0; + uint64_t lastRefresh_=0; void to_json(Poco::JSON::Object &Obj) const; bool from_json(const Poco::JSON::Object::Ptr &Obj); @@ -292,6 +293,7 @@ namespace OpenWifi { uint64_t expires=0; uint64_t idleTimeout=0; uint64_t revocationDate=0; + uint64_t lastRefresh=0; void to_json(Poco::JSON::Object &Obj) const; bool from_json(const Poco::JSON::Object::Ptr &Obj); diff --git a/src/framework/MicroService.h b/src/framework/MicroService.h index 9398107..4074792 100644 --- a/src/framework/MicroService.h +++ b/src/framework/MicroService.h @@ -104,7 +104,8 @@ namespace OpenWifi { RATE_LIMIT_EXCEEDED, BAD_MFA_TRANSACTION, MFA_FAILURE, - SECURITY_SERVICE_UNREACHABLE + SECURITY_SERVICE_UNREACHABLE, + CANNOT_REFRESH_TOKEN }; class AppServiceRegistry { @@ -652,7 +653,8 @@ namespace OpenWifi::Utils { [[nodiscard]] inline bool ValidUUID(const std::string &UUID) { if(UUID.size()>36) return false; - return (std::all_of(UUID.begin(),UUID.end(),[](auto i){return i=='-' || std::isxdigit(i);})); + uint dashes=0; + return (std::all_of(UUID.begin(),UUID.end(),[&](auto i){ if(i=='-') dashes++; return i=='-' || std::isxdigit(i);})) && (dashes>0); } [[nodiscard]] inline std::vector Split(const std::string &List, char Delimiter=',' ) { diff --git a/src/storage/orm_tokens.cpp b/src/storage/orm_tokens.cpp index 5c935d3..b9dab2e 100644 --- a/src/storage/orm_tokens.cpp +++ b/src/storage/orm_tokens.cpp @@ -26,7 +26,8 @@ namespace OpenWifi { ORM::Field{"created", ORM::FieldType::FT_BIGINT}, ORM::Field{"expires", ORM::FieldType::FT_BIGINT}, ORM::Field{"idleTimeOut", ORM::FieldType::FT_BIGINT}, - ORM::Field{"revocationDate", ORM::FieldType::FT_BIGINT} + ORM::Field{"revocationDate", ORM::FieldType::FT_BIGINT}, + ORM::Field{"lastRefresh", ORM::FieldType::FT_BIGINT} }; static ORM::IndexVec MakeIndices(const std::string &shortname) { @@ -53,6 +54,17 @@ namespace OpenWifi { return CreateRecord(T); } + bool BaseTokenDB::Upgrade([[maybe_unused]] uint32_t from, uint32_t &to) { + std::vector Statements{ + "alter table " + TableName_ + " add column lastRefresh BIGINT default 0;" + }; + RunScript(Statements); + to = 1; + return true; + + return true; + } + bool BaseTokenDB::GetToken(std::string &Token, SecurityObjects::WebToken &WT, std::string & UserId, uint64_t &RevocationDate) { SecurityObjects::Token T; @@ -64,6 +76,7 @@ namespace OpenWifi { WT.created_ = T.created; WT.expires_in_ = T.expires; WT.idle_timeout_ = T.idleTimeout; + WT.lastRefresh_ = T.lastRefresh; RevocationDate = T.revocationDate; UserId = T.userName; return true; @@ -71,6 +84,22 @@ namespace OpenWifi { return false; } + bool BaseTokenDB::RefreshToken(const std::string &OldToken, const std::string &NewToken, const std::string &NewRefreshToken, uint64_t LastRefresh ) { + SecurityObjects::Token T; + + if(GetRecord("token", OldToken, T)) { + T.token = NewToken; + T.refreshToken = NewRefreshToken; + T.lastRefresh = LastRefresh; + T.created = OpenWifi::Now(); + UpdateRecord("token",OldToken,T); + Cache_->Delete("token",OldToken); + Cache_->UpdateCache(T); + return true; + } + return false; + } + bool BaseTokenDB::IsTokenRevoked(std::string &Token) { SecurityObjects::Token T; @@ -163,6 +192,7 @@ template<> void ORM::DB(); U.idleTimeout = T.get<6>(); U.revocationDate = T.get<7>(); + U.lastRefresh = T.get<8>(); } template<> void ORM::DB< OpenWifi::TokenRecordTuple, @@ -176,4 +206,5 @@ template<> void ORM::DB< OpenWifi::TokenRecordTuple, T.set<5>(U.expires); T.set<6>(U.idleTimeout); T.set<7>(U.revocationDate); + T.set<8>(U.lastRefresh); } diff --git a/src/storage/orm_tokens.h b/src/storage/orm_tokens.h index d52226a..7c2d3e9 100644 --- a/src/storage/orm_tokens.h +++ b/src/storage/orm_tokens.h @@ -28,7 +28,8 @@ namespace OpenWifi { uint64_t, // Created = 0; uint64_t, // Expires = 0; uint64_t, // IdleTimeOut = 0; - uint64_t // RevocationDate = 0; + uint64_t, // RevocationDate = 0; + uint64_t // lastRefresh > TokenRecordTuple; typedef std::vector TokenRecordTupleList; @@ -41,7 +42,6 @@ namespace OpenWifi { void Create(const SecurityObjects::Token &R) override; bool GetFromCache(const std::string &FieldName, const std::string &Value, SecurityObjects::Token &R) override; void Delete(const std::string &FieldName, const std::string &Value) override; - private: std::mutex Mutex_; std::unique_ptr> CacheByToken_; @@ -60,6 +60,10 @@ namespace OpenWifi { bool CleanExpiredTokens(); bool RevokeAllTokens( std::string & UserName ); bool GetToken(std::string &Token, SecurityObjects::WebToken &WT, std::string & UserId, uint64_t &RevocationDate); + bool RefreshToken(const std::string &OldToken, const std::string &NewToken, const std::string &NewRefreshToken, uint64_t LstRefresh ); + inline uint32_t Version() override { return 1;} + bool Upgrade(uint32_t from, uint32_t &to) override; + private: };