Skip to content
Snippets Groups Projects
Commit f2c38a56 authored by Adrien Béraud's avatar Adrien Béraud
Browse files

server account manager: add OAuth token support

Gitlab: #226
Change-Id: I058c851795f912f261f371afe3edc2f9f09a9ff5
parent 2f043197
Branches
No related tags found
No related merge requests found
...@@ -121,7 +121,7 @@ public: ...@@ -121,7 +121,7 @@ public:
virtual void syncDevices() = 0; virtual void syncDevices() = 0;
virtual bool isPasswordValid(const std::string& password) { return false; }; virtual bool isPasswordValid(const std::string& /*password*/) { return false; };
dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const; dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const;
......
...@@ -50,6 +50,7 @@ namespace jami { ...@@ -50,6 +50,7 @@ namespace jami {
constexpr const char* const QUERY_NAME {"/name/"}; constexpr const char* const QUERY_NAME {"/name/"};
constexpr const char* const QUERY_ADDR {"/addr/"}; constexpr const char* const QUERY_ADDR {"/addr/"};
constexpr const char* const CACHE_DIRECTORY {"namecache"}; constexpr const char* const CACHE_DIRECTORY {"namecache"};
constexpr const char DEFAULT_SERVER_HOST[] = "https://ns.jami.net";
const std::string HEX_PREFIX = "0x"; const std::string HEX_PREFIX = "0x";
constexpr std::chrono::seconds SAVE_INTERVAL {5}; constexpr std::chrono::seconds SAVE_INTERVAL {5};
...@@ -68,6 +69,9 @@ toLower(std::string& string) ...@@ -68,6 +69,9 @@ toLower(std::string& string)
std::transform(string.begin(), string.end(), string.begin(), ::tolower); std::transform(string.begin(), string.end(), string.begin(), ::tolower);
} }
NameDirectory&
NameDirectory::instance() { return instance(DEFAULT_SERVER_HOST); }
void void
NameDirectory::lookupUri(const std::string& uri, const std::string& default_server, NameDirectory::lookupUri(const std::string& uri, const std::string& default_server,
LookupCallback cb) LookupCallback cb)
......
...@@ -67,7 +67,11 @@ public: ...@@ -67,7 +67,11 @@ public:
void load(); void load();
static NameDirectory& instance(const std::string& serverUrl, std::shared_ptr<dht::Logger> l = {}); static NameDirectory& instance(const std::string& serverUrl, std::shared_ptr<dht::Logger> l = {});
static NameDirectory& instance() { return instance(DEFAULT_SERVER_HOST); } static NameDirectory& instance();
void setToken(std::string token) {
serverToken_ = std::move(token);
}
static void lookupUri(const std::string& uri, const std::string& default_server, static void lookupUri(const std::string& uri, const std::string& default_server,
LookupCallback cb); LookupCallback cb);
...@@ -83,9 +87,9 @@ private: ...@@ -83,9 +87,9 @@ private:
NON_COPYABLE(NameDirectory); NON_COPYABLE(NameDirectory);
NameDirectory(NameDirectory&&) = delete; NameDirectory(NameDirectory&&) = delete;
NameDirectory& operator=(NameDirectory&&) = delete; NameDirectory& operator=(NameDirectory&&) = delete;
constexpr static const char* const DEFAULT_SERVER_HOST = "https://ns.jami.net";
std::string serverUrl_; std::string serverUrl_;
std::string serverToken_;
std::string cachePath_; std::string cachePath_;
std::mutex cacheLock_ {}; std::mutex cacheLock_ {};
......
...@@ -32,11 +32,11 @@ namespace jami { ...@@ -32,11 +32,11 @@ namespace jami {
using Request = dht::http::Request; using Request = dht::http::Request;
static const std::string PATH_LOGIN = "/api/login";
static const std::string PATH_AUTH = "/api/auth"; static const std::string PATH_AUTH = "/api/auth";
static const std::string PATH_DEVICE = PATH_AUTH + "/device"; static const std::string PATH_DEVICE = PATH_AUTH + "/device";
static const std::string PATH_DEVICES = PATH_AUTH + "/devices"; static const std::string PATH_DEVICES = PATH_AUTH + "/devices";
static const std::string PATH_SEARCH = PATH_AUTH + "/directory/search";
constexpr const char* const HTTPS_PROTO {"https"};
ServerAccountManager::ServerAccountManager( ServerAccountManager::ServerAccountManager(
const std::string& path, const std::string& path,
...@@ -52,10 +52,8 @@ ServerAccountManager::ServerAccountManager( ...@@ -52,10 +52,8 @@ ServerAccountManager::ServerAccountManager(
{}; {};
void void
ServerAccountManager::setHeaderFields(Request& request){ ServerAccountManager::setAuthHeaderFields(Request& request) const {
request.set_header_field(restinio::http_field_t::user_agent, "Jami"); request.set_header_field(restinio::http_field_t::authorization, "Bearer " + token_);
request.set_header_field(restinio::http_field_t::accept, "application/json");
request.set_header_field(restinio::http_field_t::content_type, "application/json");
} }
void void
...@@ -81,27 +79,19 @@ ServerAccountManager::initAuthentication( ...@@ -81,27 +79,19 @@ ServerAccountManager::initAuthentication(
onChange_ = std::move(onChange); onChange_ = std::move(onChange);
const std::string url = managerHostname_ + PATH_DEVICE; const std::string url = managerHostname_ + PATH_DEVICE;
JAMI_WARN("[Auth] authentication with: %s to %s", ctx->credentials->username.c_str(), url.c_str()); JAMI_WARN("[Auth] authentication with: %s to %s", ctx->credentials->username.c_str(), url.c_str());
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
auto reqid = request->id();
request->set_method(restinio::http_method_post());
request->set_auth(ctx->credentials->username, ctx->credentials->password);
requests_[reqid] = request;
dht::ThreadPool::computation().run([onAsync = onAsync_, ctx, request, reqid]{ dht::ThreadPool::computation().run([onAsync = onAsync_, ctx, url]{
onAsync([ctx, request, reqid, onAsync](AccountManager& accountManager){ onAsync([=](AccountManager& accountManager){
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager); auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
Json::Value body;
{ {
std::stringstream ss; std::stringstream ss;
auto csr = ctx->request.get()->toString(); auto csr = ctx->request.get()->toString();
string_replace(csr, "\n", "\\n"); body["csr"] = csr;
string_replace(csr, "\r", "\\r"); body["deviceName"] = ctx->deviceName;
ss << "{\"csr\":\"" << csr << "\", \"deviceName\":\"" << ctx->deviceName << "\"}"; }
JAMI_WARN("[Auth] Sending request: %s", csr.c_str()); auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, body, [ctx, onAsync]
request->set_body(ss.str()); (Json::Value json, const dht::http::Response& response){
}
this_.setHeaderFields(*request);
request->add_on_done_callback([reqid, request, ctx, onAsync]
(const dht::http::Response& response){
JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code); JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code);
if (response.status_code == 0) if (response.status_code == 0)
ctx->onFailure(AuthError::SERVER_ERROR, "Can't connect to server"); ctx->onFailure(AuthError::SERVER_ERROR, "Can't connect to server");
...@@ -112,22 +102,8 @@ ServerAccountManager::initAuthentication( ...@@ -112,22 +102,8 @@ ServerAccountManager::initAuthentication(
else { else {
do { do {
try { try {
Json::Value json;
std::string err;
Json::CharReaderBuilder rbuilder;
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse server response");
break;
}
JAMI_WARN("[Auth] Got server response: %s", response.body.c_str()); JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
auto cert = std::make_shared<dht::crypto::Certificate>(json["certificateChain"].asString());
auto certStr = json["certificateChain"].asString();
string_replace(certStr, "\\n", "\n");
string_replace(certStr, "\\r", "\r");
auto cert = std::make_shared<dht::crypto::Certificate>(certStr);
auto accountCert = cert->issuer; auto accountCert = cert->issuer;
if (not accountCert) { if (not accountCert) {
JAMI_ERR("[Auth] Can't parse certificate: no issuer"); JAMI_ERR("[Auth] Can't parse certificate: no issuer");
...@@ -136,18 +112,14 @@ ServerAccountManager::initAuthentication( ...@@ -136,18 +112,14 @@ ServerAccountManager::initAuthentication(
} }
auto receipt = json["deviceReceipt"].asString(); auto receipt = json["deviceReceipt"].asString();
Json::Value receiptJson; Json::Value receiptJson;
auto receiptReader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); std::string err;
auto receiptReader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder{}.newCharReader());
if (!receiptReader->parse(receipt.data(), receipt.data() + receipt.size(), &receiptJson, &err)){ if (!receiptReader->parse(receipt.data(), receipt.data() + receipt.size(), &receiptJson, &err)){
JAMI_ERR("[Auth] Can't parse receipt from server: %s", err.c_str()); JAMI_ERR("[Auth] Can't parse receipt from server: %s", err.c_str());
ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse receipt from server"); ctx->onFailure(AuthError::SERVER_ERROR, "Can't parse receipt from server");
break; break;
} }
onAsync([reqid, ctx, onAsync([=] (AccountManager& accountManager) mutable
json=std::move(json),
receiptJson=std::move(receiptJson),
cert,
accountCert,
receipt=std::move(receipt)] (AccountManager& accountManager) mutable
{ {
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager); auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
auto receiptSignature = base64::decode(json["receiptSignature"].asString()); auto receiptSignature = base64::decode(json["receiptSignature"].asString());
...@@ -195,42 +167,131 @@ ServerAccountManager::initAuthentication( ...@@ -195,42 +167,131 @@ ServerAccountManager::initAuthentication(
} }
} while (false); } while (false);
} }
onAsync([reqid](AccountManager& accountManager){
onAsync([response](AccountManager& accountManager){
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager); auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
this_.requests_.erase(reqid); this_.clearRequest(response.request);
}); });
}, this_.logger_);
request->set_auth(ctx->credentials->username, ctx->credentials->password);
this_.sendRequest(request);
}); });
request->send();
}); });
}
void
ServerAccountManager::authenticateDevice() {
const std::string url = managerHostname_ + PATH_LOGIN;
JAMI_WARN("[Auth] getting a device token: %s", url.c_str());
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, Json::Value{Json::objectValue}, [onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
onAsync([=] (AccountManager& accountManager) {
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
if (response.status_code >= 200 && response.status_code < 300) {
auto scopeStr = json["scope"].asString();
auto scope = scopeStr == "DEVICE" ? TokenScope::Device
: (scopeStr == "USER" ? TokenScope::User
: TokenScope::None);
JAMI_WARN("[Auth] Got server response: %d %s", response.status_code, response.body.c_str());
this_.setToken(json["access_token"].asString(), scope);
} else {
this_.authFailed(TokenScope::Device, response.status_code);
}
this_.clearRequest(response.request);
}); });
}, logger_);
request->set_identity(info_->identity);
// request->set_certificate_authority(info_->identity.second->issuer->issuer);
sendRequest(request);
}
void
ServerAccountManager::sendRequest(const std::shared_ptr<dht::http::Request>& request) {
request->set_header_field(restinio::http_field_t::user_agent, "Jami");
{
std::lock_guard<std::mutex> lock(requestLock_);
requests_.emplace(request);
}
request->send();
}
void
ServerAccountManager::clearRequest(const std::weak_ptr<dht::http::Request>& request) {
if (auto req = request.lock()) {
std::lock_guard<std::mutex> lock(requestLock_);
requests_.erase(req);
}
}
void
ServerAccountManager::authFailed(TokenScope scope, int code)
{
RequestQueue requests;
{
std::lock_guard<std::mutex> lock(tokenLock_);
requests = std::move(getRequestQueue(scope));
}
JAMI_DBG("[Auth] Failed auth with scope %d, ending %zu pending requests", (int)scope, requests.size());
while (not requests.empty()) {
auto req = std::move(requests.front());
requests.pop();
req->terminate(code == 0 ? asio::error::not_connected : asio::error::access_denied);
}
}
void
ServerAccountManager::setToken(std::string token, TokenScope scope)
{
std::lock_guard<std::mutex> lock(tokenLock_);
token_ = std::move(token);
nameDir_.get().setToken(token_);
if (not token_.empty()) {
auto& reqQueue = getRequestQueue(scope);
JAMI_DBG("[Auth] Got token with scope %d, handling %zu pending requests", (int)scope, reqQueue.size());
while (not reqQueue.empty()) {
auto req = std::move(reqQueue.front());
reqQueue.pop();
setAuthHeaderFields(*req);
sendRequest(req);
}
}
}
void ServerAccountManager::sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req)
{
std::lock_guard<std::mutex> lock(tokenLock_);
if (hasAuthorization(TokenScope::Device)) {
setAuthHeaderFields(*req);
sendRequest(req);
} else {
auto& rQueue = getRequestQueue(TokenScope::Device);
if (rQueue.empty())
authenticateDevice();
rQueue.emplace(req);
}
}
void ServerAccountManager::sendAccountRequest(const std::shared_ptr<dht::http::Request>& req)
{
std::lock_guard<std::mutex> lock(tokenLock_);
if (hasAuthorization(TokenScope::User)) {
setAuthHeaderFields(*req);
sendRequest(req);
} else {
pendingAccountRequests_.emplace(req);
}
} }
void void
ServerAccountManager::syncDevices() ServerAccountManager::syncDevices()
{ {
if (not creds_) const std::string url = managerHostname_ + PATH_DEVICES;
return; JAMI_WARN("[Auth] syncDevices %s", url.c_str());
const std::string url = managerHostname_ + PATH_DEVICES + "?username=" + creds_->username; sendDeviceRequest(std::make_shared<Request>(*Manager::instance().ioContext(), url, [onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
JAMI_WARN("[Auth] syncDevices with: %s to %s", creds_->username.c_str(), url.c_str()); onAsync([=] (AccountManager& accountManager) {
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_);
auto reqid = request->id();
request->set_method(restinio::http_method_get());
request->set_auth(creds_->username, creds_->password);
setHeaderFields(*request);
request->add_on_done_callback([reqid, onAsync = onAsync_] (const dht::http::Response& response){
onAsync([reqid, response] (AccountManager& accountManager) {
JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code); JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code);
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager); auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
if (response.status_code >= 200 || response.status_code < 300) { if (response.status_code >= 200 && response.status_code < 300) {
try { try {
Json::Value json;
std::string err;
Json::CharReaderBuilder rbuilder;
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
JAMI_ERR("[Auth] Can't parse server response: %s", err.c_str());
return;
}
JAMI_WARN("[Auth] Got server response: %s", response.body.c_str()); JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
if (not json.isArray()) { if (not json.isArray()) {
JAMI_ERR("[Auth] Can't parse server response: not an array"); JAMI_ERR("[Auth] Can't parse server response: not an array");
...@@ -248,11 +309,9 @@ ServerAccountManager::syncDevices() ...@@ -248,11 +309,9 @@ ServerAccountManager::syncDevices()
JAMI_ERR("Error when loading device list: %s", e.what()); JAMI_ERR("Error when loading device list: %s", e.what());
} }
} }
this_.requests_.erase(reqid); this_.clearRequest(response.request);
}); });
}); }, logger_));
request->send();
requests_[reqid] = std::move(request);
} }
bool bool
...@@ -265,27 +324,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin ...@@ -265,27 +324,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin
} }
const std::string url = managerHostname_ + PATH_DEVICE + "?deviceId=" + device; const std::string url = managerHostname_ + PATH_DEVICE + "?deviceId=" + device;
JAMI_WARN("[Revoke] Removing device of %s at %s", info_->username.c_str(), url.c_str()); JAMI_WARN("[Revoke] Removing device of %s at %s", info_->username.c_str(), url.c_str());
auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, logger_); auto request = std::make_shared<Request>(*Manager::instance().ioContext(), url, [cb, onAsync = onAsync_] (Json::Value json, const dht::http::Response& response){
auto reqid = request->id(); onAsync([=] (AccountManager& accountManager) {
request->set_method(restinio::http_method_delete());
request->set_auth(info_->username, password);
setHeaderFields(*request);
request->add_on_done_callback([reqid, cb, onAsync = onAsync_] (const dht::http::Response& response){
onAsync([reqid, cb, response] (AccountManager& accountManager) {
JAMI_DBG("[Revoke] Got request callback with status code=%u", response.status_code); JAMI_DBG("[Revoke] Got request callback with status code=%u", response.status_code);
auto& this_ = *static_cast<ServerAccountManager*>(&accountManager); auto& this_ = *static_cast<ServerAccountManager*>(&accountManager);
if (response.status_code >= 200 || response.status_code < 300) { if (response.status_code >= 200 && response.status_code < 300) {
if (response.body.empty())
return;
try { try {
Json::Value json; JAMI_WARN("[Revoke] Got server response");
std::string err;
Json::CharReaderBuilder rbuilder;
auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err)){
JAMI_ERR("[Revoke] Can't parse server response: %s", err.c_str());
}
JAMI_WARN("[Revoke] Got server response: %s", response.body.c_str());
if (json["errorDetails"].empty()){ if (json["errorDetails"].empty()){
if (cb) if (cb)
cb(RevokeDeviceResult::SUCCESS); cb(RevokeDeviceResult::SUCCESS);
...@@ -298,12 +343,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin ...@@ -298,12 +343,13 @@ ServerAccountManager::revokeDevice(const std::string& password, const std::strin
} }
else if (cb) else if (cb)
cb(RevokeDeviceResult::ERROR_NETWORK); cb(RevokeDeviceResult::ERROR_NETWORK);
this_.requests_.erase(reqid); this_.clearRequest(response.request);
});
}); });
}, logger_);
request->set_method(restinio::http_method_delete());
request->set_auth(info_->username, password);
JAMI_DBG("[Revoke] Sending revoke device '%s' to JAMS", device.c_str()); JAMI_DBG("[Revoke] Sending revoke device '%s' to JAMS", device.c_str());
request->send(); sendRequest(request);
requests_[reqid] = std::move(request);
return false; return false;
} }
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
#include "account_manager.h" #include "account_manager.h"
#include <queue>
#include <set>
namespace jami { namespace jami {
class ServerAccountManager : public AccountManager { class ServerAccountManager : public AccountManager {
...@@ -64,10 +67,42 @@ private: ...@@ -64,10 +67,42 @@ private:
const std::string managerHostname_; const std::string managerHostname_;
std::shared_ptr<dht::Logger> logger_; std::shared_ptr<dht::Logger> logger_;
std::map<unsigned int /*id*/, std::shared_ptr<dht::http::Request>> requests_;
std::mutex requestLock_;
std::set<std::shared_ptr<dht::http::Request>> requests_;
std::unique_ptr<ServerAccountCredentials> creds_; std::unique_ptr<ServerAccountCredentials> creds_;
void setHeaderFields(dht::http::Request& request); void sendRequest(const std::shared_ptr<dht::http::Request>& request);
void clearRequest(const std::weak_ptr<dht::http::Request>& request);
enum class TokenScope : unsigned {
None = 0,
Device,
User,
Admin
};
std::mutex tokenLock_;
TokenScope tokenScope_ {};
std::string token_ {};
using RequestQueue = std::queue<std::shared_ptr<dht::http::Request>>;
RequestQueue pendingDeviceRequests_;
RequestQueue pendingAccountRequests_;
RequestQueue& getRequestQueue(TokenScope scope) {
return scope == TokenScope::Device ? pendingDeviceRequests_ : pendingAccountRequests_;
}
bool hasAuthorization(TokenScope scope) const {
return not token_.empty() and tokenScope_ >= scope;
}
void setAuthHeaderFields(dht::http::Request& request) const;
void sendDeviceRequest(const std::shared_ptr<dht::http::Request>& req);
void sendAccountRequest(const std::shared_ptr<dht::http::Request>& req);
void authenticateDevice();
void authenticateAccount();
void authFailed(TokenScope scope, int code);
void setToken(std::string token, TokenScope scope);
}; };
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment