| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| /* |
| * M-Pin SDK implementation |
| */ |
| |
| |
| #include "mpin_sdk.h" |
| #include "mpin_crypto_non_tee.h" |
| #include "version.h" |
| #include "json/visitor.h" |
| #include <sstream> |
| |
| typedef MPinSDK::Status Status; |
| typedef MPinSDK::User User; |
| typedef MPinSDK::UserPtr UserPtr; |
| typedef MPinSDK::String String; |
| typedef MPinSDK::StringMap StringMap; |
| typedef MPinSDK::IHttpRequest::Method HttpMethod; |
| |
| /* |
| * Status class |
| */ |
| |
| Status::Status() : m_statusCode(OK) |
| { |
| } |
| |
| Status::Status(Code statucCode) : m_statusCode(statucCode) |
| { |
| } |
| |
| Status::Status(Code statucCode, const String& error) : m_statusCode(statucCode), m_errorMessage(error) |
| { |
| } |
| |
| Status::Code Status::GetStatusCode() const |
| { |
| return m_statusCode; |
| } |
| |
| const String& Status::GetErrorMessage() const |
| { |
| return m_errorMessage; |
| } |
| |
| void Status::SetStatusCode(Code statusCode) |
| { |
| m_statusCode = statusCode; |
| } |
| |
| void Status::SetErrorMessage(const String& error) |
| { |
| m_errorMessage = error; |
| } |
| |
| bool Status::operator==(Code statusCode) const |
| { |
| return m_statusCode == statusCode; |
| } |
| |
| bool Status::operator!=(Code statusCode) const |
| { |
| return m_statusCode != statusCode; |
| } |
| |
| |
| /* |
| * TimePermitCache class |
| */ |
| |
| MPinSDK::TimePermitCache::TimePermitCache() : m_date(0) |
| { |
| } |
| |
| const String& MPinSDK::TimePermitCache::GetTimePermit() const |
| { |
| return m_timePermit; |
| } |
| |
| int MPinSDK::TimePermitCache::GetDate() const |
| { |
| return m_date; |
| } |
| |
| void MPinSDK::TimePermitCache::Set(const String& timePermit, int date) |
| { |
| m_timePermit = timePermit; |
| m_date = date; |
| } |
| |
| void MPinSDK::TimePermitCache::Invalidate() |
| { |
| m_timePermit.Overwrite(); |
| m_date = 0; |
| } |
| |
| |
| /* |
| * User class |
| */ |
| |
| User::User(const String& id, const String& deviceName) : m_id(id), m_deviceName(deviceName), m_state(INVALID) |
| { |
| } |
| |
| const String& User::GetId() const |
| { |
| return m_id; |
| } |
| |
| const String& User::GetDeviceName() const |
| { |
| return m_deviceName; |
| } |
| |
| User::State User::GetState() const |
| { |
| return m_state; |
| } |
| |
| const String& User::GetMPinId() const |
| { |
| return m_mpinId; |
| } |
| |
| const String& User::GetMPinIdHex() const |
| { |
| return m_mpinIdHex; |
| } |
| |
| const String& User::GetRegOTT() const |
| { |
| return m_regOTT; |
| } |
| |
| const MPinSDK::TimePermitCache& User::GetTimePermitCache() const |
| { |
| return m_timePermitCache; |
| } |
| |
| void User::CacheTimePermit(const String& timePermit, int date) |
| { |
| m_timePermitCache.Set(timePermit, date); |
| } |
| |
| void User::SetStartedRegistration(const String& mpinIdHex, const String& regOTT) |
| { |
| m_state = STARTED_REGISTRATION; |
| m_mpinIdHex = mpinIdHex; |
| m_mpinId = util::HexDecode(mpinIdHex); |
| m_regOTT = regOTT; |
| } |
| |
| void User::SetActivated() |
| { |
| m_state = ACTIVATED; |
| } |
| |
| void User::SetRegistered() |
| { |
| m_regOTT.Overwrite(); |
| m_state = REGISTERED; |
| } |
| |
| void User::Invalidate() |
| { |
| m_regOTT.Overwrite(); |
| m_timePermitCache.Invalidate(); |
| m_state = INVALID; |
| } |
| |
| void User::Block() |
| { |
| Invalidate(); |
| m_state = BLOCKED; |
| } |
| |
| Status User::RestoreState(const String& stateString, const String& mpinIdHex, const String& regOTT) |
| { |
| SetStartedRegistration(mpinIdHex, regOTT); |
| |
| State state = StringToState(stateString); |
| switch(state) |
| { |
| case INVALID: |
| return Status(Status::STORAGE_ERROR, String().Format("Invalid user state found for user '%s': '%s'", m_id.c_str(), stateString.c_str())); |
| case ACTIVATED: |
| SetActivated(); |
| case STARTED_REGISTRATION: |
| // regOTT *must* be valid |
| if(regOTT.empty()) |
| { |
| return Status(Status::STORAGE_ERROR, String().Format("Corrupted data for user '%s': state is '%s' but no regOTT was found", m_id.c_str(), stateString.c_str())); |
| } |
| return Status(Status::OK); |
| case REGISTERED: |
| SetRegistered(); |
| // regOTT *must* be empty |
| if(!regOTT.empty()) |
| { |
| return Status(Status::STORAGE_ERROR, String().Format("Corrupted data for user '%s': state is '%s' but regOTT is still saved", m_id.c_str(), stateString.c_str())); |
| } |
| return Status(Status::OK); |
| case BLOCKED: |
| Block(); |
| return Status(Status::OK); |
| default: |
| assert(false); |
| return Status(Status::STORAGE_ERROR, String().Format("Internal error: invalid state %d returned from User::StringToState(%s)", (int) state, stateString.c_str())); |
| } |
| } |
| |
| String User::GetStateString() const |
| { |
| return User::StateToString(m_state); |
| } |
| |
| String User::StateToString(State state) |
| { |
| switch(state) |
| { |
| case STARTED_REGISTRATION: |
| return "STARTED_REGISTRATION"; |
| case ACTIVATED: |
| return "ACTIVATED"; |
| case REGISTERED: |
| return "REGISTERED"; |
| case BLOCKED: |
| return "BLOCKED"; |
| default: |
| assert(false); |
| return "INVALID"; |
| } |
| } |
| |
| User::State User::StringToState(const String& stateString) |
| { |
| if(stateString == StateToString(STARTED_REGISTRATION)) |
| { |
| return STARTED_REGISTRATION; |
| } |
| if(stateString == StateToString(ACTIVATED)) |
| { |
| return ACTIVATED; |
| } |
| if(stateString == StateToString(REGISTERED)) |
| { |
| return REGISTERED; |
| } |
| if(stateString == StateToString(BLOCKED)) |
| { |
| return BLOCKED; |
| } |
| |
| return INVALID; |
| } |
| |
| |
| /* |
| * MPinSDK::IHttpRequest static string constants |
| */ |
| |
| const char *MPinSDK::IHttpRequest::CONTENT_TYPE_HEADER = "Content-Type"; |
| const char *MPinSDK::IHttpRequest::ACCEPT_HEADER = "Accept"; |
| #define JSON_CONTENT_TYPE_STRING "application/json" |
| const char *MPinSDK::IHttpRequest::JSON_CONTENT_TYPE = JSON_CONTENT_TYPE_STRING "; charset=UTF-8"; |
| const char *MPinSDK::IHttpRequest::TEXT_PLAIN_CONTENT_TYPE = "text/plain"; |
| |
| |
| /* |
| * MPinSDK::HttpResponse class |
| */ |
| |
| MPinSDK::HttpResponse::HttpResponse(const String& requestUrl, const String& requestBody) |
| : m_httpStatus(HTTP_OK), m_dataType(JSON), m_requestUrl(requestUrl), m_requestBody(requestBody) |
| { |
| } |
| |
| int MPinSDK::HttpResponse::GetStatus() const |
| { |
| return m_httpStatus; |
| } |
| |
| MPinSDK::HttpResponse::DataType MPinSDK::HttpResponse::GetDataType() const |
| { |
| return m_dataType; |
| } |
| |
| MPinSDK::HttpResponse::DataType MPinSDK::HttpResponse::DetermineDataType(const String& contentTypeStr) const |
| { |
| if(contentTypeStr.compare(0, strlen(JSON_CONTENT_TYPE_STRING), JSON_CONTENT_TYPE_STRING) == 0) |
| { |
| return JSON; |
| } |
| |
| return RAW; |
| } |
| |
| bool MPinSDK::HttpResponse::SetData(const String& rawData, const StringMap& headers, DataType expectedType) |
| { |
| m_rawData = rawData; |
| m_headers = headers; |
| |
| String contentTypeStr = headers.Get(IHttpRequest::CONTENT_TYPE_HEADER); |
| m_dataType = DetermineDataType(contentTypeStr); |
| |
| String data = rawData; |
| data.Trim(); |
| if(data.length() > 0 && !m_jsonData.Parse(data.c_str())) |
| { |
| SetResponseJsonParseError(data, m_jsonData.GetParseError()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| const util::JsonObject& MPinSDK::HttpResponse::GetJsonData() const |
| { |
| return m_jsonData; |
| } |
| |
| const String& MPinSDK::HttpResponse::GetRawData() const |
| { |
| return m_rawData; |
| } |
| |
| const StringMap& MPinSDK::HttpResponse::GetHeaders() const |
| { |
| return m_headers; |
| } |
| |
| void MPinSDK::HttpResponse::SetNetworkError(const String& error) |
| { |
| m_httpStatus = NON_HTTP_ERROR; |
| m_mpinStatus.SetStatusCode(Status::NETWORK_ERROR); |
| m_mpinStatus.SetErrorMessage(String().Format("HTTP request to '%s' failed. Error: '%s'", m_requestUrl.c_str(), error.c_str())); |
| } |
| |
| void MPinSDK::HttpResponse::SetResponseJsonParseError(const String& responseJson, const String& jsonParseError) |
| { |
| m_httpStatus = NON_HTTP_ERROR; |
| m_mpinStatus.SetStatusCode(Status::RESPONSE_PARSE_ERROR); |
| m_mpinStatus.SetErrorMessage(String().Format("Failed to parse '%s' response json in request to '%s' (body='%s'). Error: '%s'", |
| responseJson.c_str(), m_requestUrl.c_str(), m_requestBody.c_str(), jsonParseError.c_str())); |
| } |
| |
| void MPinSDK::HttpResponse::SetUnexpectedContentTypeError(DataType expectedType, const String& responseContentType, const String& responseRawData) |
| { |
| m_httpStatus = NON_HTTP_ERROR; |
| m_mpinStatus.SetStatusCode(Status::RESPONSE_PARSE_ERROR); |
| assert(expectedType == JSON || expectedType == RAW); |
| m_mpinStatus.SetErrorMessage(String().Format("HTTP request to '%s' (body='%s') returned unexpected content type '%s'. Expected was '%s'", |
| m_requestUrl.c_str(), m_requestBody.c_str(), responseContentType.c_str(), (expectedType == JSON) ? "JSON" : "RAW")); |
| } |
| |
| void MPinSDK::HttpResponse::SetHttpError(int httpStatus) |
| { |
| m_httpStatus = httpStatus; |
| |
| m_mpinStatus.SetErrorMessage(String().Format("HTTP request to '%s' (body='%s') returned status code %d", |
| m_requestUrl.c_str(), m_requestBody.c_str(), httpStatus)); |
| |
| if(httpStatus >= 500) |
| { |
| m_mpinStatus.SetStatusCode(Status::HTTP_SERVER_ERROR); |
| } |
| else if(httpStatus >= 400) |
| { |
| m_mpinStatus.SetStatusCode(Status::HTTP_REQUEST_ERROR); |
| } |
| else if(httpStatus >= 300) |
| { |
| m_mpinStatus.SetStatusCode(Status::NETWORK_ERROR); |
| } |
| else |
| { |
| // TODO: What to do if server returns 2xx (but not 200) or 3xx? |
| assert(false); |
| } |
| } |
| |
| Status MPinSDK::HttpResponse::TranslateToMPinStatus(Context context) |
| { |
| switch(context) |
| { |
| case GET_CLIENT_SETTINGS: |
| case AUTHENTICATE_PASS1: |
| case AUTHENTICATE_PASS2: |
| break; |
| case REGISTER: |
| if(m_httpStatus == HTTP_FORBIDDEN) |
| { |
| m_mpinStatus.SetStatusCode(Status::IDENTITY_NOT_AUTHORIZED); |
| m_mpinStatus.SetErrorMessage("Identity not authorized"); |
| } |
| break; |
| case GET_CLIENT_SECRET1: |
| if(m_httpStatus == HTTP_BAD_REQUEST || m_httpStatus == HTTP_UNAUTHORIZED) |
| { |
| m_mpinStatus.SetStatusCode(Status::IDENTITY_NOT_VERIFIED); |
| m_mpinStatus.SetErrorMessage("Identity not verified"); |
| } |
| break; |
| case GET_CLIENT_SECRET2: |
| if(m_httpStatus == HTTP_REQUEST_TIMEOUT) |
| { |
| m_mpinStatus.SetStatusCode(Status::REQUEST_EXPIRED); |
| m_mpinStatus.SetErrorMessage("Request expired"); |
| } |
| break; |
| case GET_TIME_PERMIT1: |
| case GET_TIME_PERMIT2: |
| if(m_httpStatus == HTTP_GONE) |
| { |
| m_mpinStatus.SetStatusCode(Status::REVOKED); |
| m_mpinStatus.SetErrorMessage("User revoked"); |
| } |
| break; |
| case AUTHENTICATE_RPA: |
| if(m_httpStatus == HTTP_UNAUTHORIZED) |
| { |
| m_mpinStatus.SetStatusCode(Status::INCORRECT_PIN); |
| m_mpinStatus.SetErrorMessage("Incorrect pin"); |
| } |
| else if(m_httpStatus == HTTP_REQUEST_TIMEOUT) |
| { |
| m_mpinStatus.SetStatusCode(Status::REQUEST_EXPIRED); |
| m_mpinStatus.SetErrorMessage("Request expired"); |
| } |
| else if(m_httpStatus == HTTP_GONE) |
| { |
| m_mpinStatus.SetStatusCode(Status::INCORRECT_PIN); |
| m_mpinStatus.SetErrorMessage("User blocked"); |
| } |
| else if(m_httpStatus == HTTP_PRECONDITION_FAILED) |
| { |
| m_mpinStatus.SetStatusCode(Status::INCORRECT_ACCESS_NUMBER); |
| m_mpinStatus.SetErrorMessage("Invalid access number"); |
| } |
| else if(m_httpStatus == HTTP_FORBIDDEN) |
| { |
| m_mpinStatus.SetStatusCode(Status::IDENTITY_NOT_AUTHORIZED); |
| m_mpinStatus.SetErrorMessage("Identity not authorized"); |
| } |
| break; |
| } |
| |
| return m_mpinStatus; |
| } |
| |
| |
| /* |
| * MPinSDK class |
| */ |
| |
| const char *MPinSDK::DEFAULT_RPS_PREFIX = "rps"; |
| const char *MPinSDK::CONFIG_BACKEND = "backend"; |
| // TODO: Remove this |
| static const char *CONFIG_BACKEND_OLD = "RPA_server"; |
| const char *MPinSDK::CONFIG_RPS_PREFIX = "rps_prefix"; |
| |
| MPinSDK::MPinSDK() : m_state(NOT_INITIALIZED), m_context(NULL), m_crypto(NULL) |
| { |
| } |
| |
| MPinSDK::~MPinSDK() |
| { |
| Destroy(); |
| } |
| |
| bool MPinSDK::IsInitilized() const |
| { |
| return m_state != NOT_INITIALIZED; |
| } |
| |
| bool MPinSDK::IsBackendSet() const |
| { |
| return m_state == BACKEND_SET; |
| } |
| |
| Status MPinSDK::CheckIfIsInitialized() const |
| { |
| if(IsInitilized()) |
| { |
| return Status(Status::OK); |
| } |
| |
| return Status(Status::FLOW_ERROR, "MPinSDK not initialized"); |
| } |
| |
| Status MPinSDK::CheckIfBackendIsSet() const |
| { |
| if(IsBackendSet()) |
| { |
| return Status(Status::OK); |
| } |
| |
| return Status(Status::FLOW_ERROR, "MPinSDK backend was not set"); |
| } |
| |
| MPinSDK::HttpResponse MPinSDK::MakeRequest(const String& url, HttpMethod method, const util::JsonObject& bodyJson, HttpResponse::DataType expectedResponseType) const |
| { |
| IHttpRequest *r = m_context->CreateHttpRequest(); |
| String requestBody = bodyJson.ToString(); |
| HttpResponse response(url, requestBody); |
| |
| if(method != IHttpRequest::GET) |
| { |
| StringMap headers; |
| headers.Put(IHttpRequest::CONTENT_TYPE_HEADER, IHttpRequest::JSON_CONTENT_TYPE); |
| headers.Put(IHttpRequest::ACCEPT_HEADER, IHttpRequest::TEXT_PLAIN_CONTENT_TYPE); |
| r->SetHeaders(headers); |
| r->SetContent(requestBody); |
| } |
| |
| if(!r->Execute(method, url)) |
| { |
| response.SetNetworkError(r->GetExecuteErrorMessage()); |
| m_context->ReleaseHttpRequest(r); |
| return response; |
| } |
| |
| int httpStatus = r->GetHttpStatusCode(); |
| if(httpStatus != HttpResponse::HTTP_OK) |
| { |
| response.SetHttpError(httpStatus); |
| m_context->ReleaseHttpRequest(r); |
| return response; |
| } |
| |
| if ( !r->GetResponseData().empty() ) |
| { |
| response.SetData(r->GetResponseData(), r->GetResponseHeaders(), expectedResponseType); |
| } |
| |
| m_context->ReleaseHttpRequest(r); |
| return response; |
| } |
| |
| MPinSDK::HttpResponse MPinSDK::MakeGetRequest(const String& url, HttpResponse::DataType expectedResponseType) const |
| { |
| return MakeRequest(url, IHttpRequest::GET, util::JsonObject(), expectedResponseType); |
| } |
| |
| class RewriteUrlVisitor : public json::Visitor |
| { |
| public: |
| RewriteUrlVisitor(const String& rpaServer) : m_rpaServer(rpaServer) {} |
| virtual ~RewriteUrlVisitor() {} |
| |
| virtual void Visit(json::Array& array) {} |
| virtual void Visit(json::Object& object) {} |
| virtual void Visit(json::Number& number) {} |
| virtual void Visit(json::Boolean& boolean) {} |
| virtual void Visit(json::Null& null) {} |
| |
| virtual void Visit(json::String& string) |
| { |
| String url = string.Value(); |
| if (url[0] == '/') |
| { |
| url.insert(0, m_rpaServer); |
| } |
| else |
| { |
| // Replace wss:// with https:// and ws:// with http:// |
| url.ReplaceAll("wss://", "https://"); |
| url.ReplaceAll("ws://", "http://"); |
| } |
| string = url; |
| } |
| |
| private: |
| String m_rpaServer; |
| }; |
| |
| Status MPinSDK::RewriteRelativeUrls() |
| { |
| try |
| { |
| RewriteUrlVisitor visitor(m_RPAServer); |
| for(json::Object::iterator i = m_clientSettings.Begin(); i != m_clientSettings.End(); ++i) |
| { |
| i->element.Accept(visitor); |
| } |
| } |
| catch(json::Exception&) |
| { |
| return Status(Status::RESPONSE_PARSE_ERROR, String().Format("Unexpected client settings json: '%s'", m_clientSettings.ToString().c_str())); |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| Status MPinSDK::Init(const StringMap& config, IContext* ctx) |
| { |
| if(IsInitilized()) |
| { |
| return Status(Status::OK); |
| } |
| |
| m_context = ctx; |
| |
| if(ctx->GetMPinCryptoType() == CRYPTO_NON_TEE) |
| { |
| MPinCryptoNonTee *nonteeCrypto = new MPinCryptoNonTee(); |
| Status s = nonteeCrypto->Init(ctx->GetStorage(IStorage::SECURE)); |
| if(s != Status::OK) |
| { |
| delete nonteeCrypto; |
| return s; |
| } |
| m_crypto = nonteeCrypto; |
| } |
| else |
| { |
| return Status(Status::FLOW_ERROR, String("CRYPTO_TEE crypto type is currently not supported")); |
| } |
| |
| m_state = INITIALIZED; |
| |
| String backend = config.Get(CONFIG_BACKEND); |
| if(backend.empty()) |
| { |
| backend = config.Get(CONFIG_BACKEND_OLD); |
| if(backend.empty()) |
| { |
| return Status(Status::OK); |
| } |
| } |
| |
| StringMap::const_iterator i = config.find(CONFIG_RPS_PREFIX); |
| String rpsPrefix = (i != config.end()) ? i->second : DEFAULT_RPS_PREFIX; |
| |
| return SetBackend(backend, rpsPrefix); |
| } |
| |
| void MPinSDK::Destroy() |
| { |
| if(!IsInitilized()) |
| { |
| return; |
| } |
| |
| ClearUsers(); |
| |
| delete m_crypto; |
| m_crypto = NULL; |
| m_context = NULL; |
| |
| m_state = NOT_INITIALIZED; |
| } |
| |
| void MPinSDK::ClearUsers() |
| { |
| for (UsersMap::iterator i = m_users.begin(); i != m_users.end(); ++i) |
| { |
| i->second->Invalidate(); |
| } |
| m_users.clear(); |
| m_logoutData.clear(); |
| } |
| |
| Status MPinSDK::GetClientSettings(const String& backend, const String& rpsPrefix, OUT util::JsonObject *clientSettings) const |
| { |
| HttpResponse response = MakeGetRequest(String().Format("%s/%s/clientSettings", backend.c_str(), String(rpsPrefix).Trim("/").c_str())); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::GET_CLIENT_SETTINGS); |
| } |
| |
| if(clientSettings != NULL) |
| { |
| *clientSettings = response.GetJsonData(); |
| } |
| |
| |
| return Status(Status::OK); |
| } |
| |
| Status MPinSDK::TestBackend(const String& backend, const String& rpsPrefix) const |
| { |
| Status s = CheckIfIsInitialized(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| return GetClientSettings(String(backend).TrimRight("/"), rpsPrefix, NULL); |
| } |
| |
| Status MPinSDK::SetBackend(const String& backend, const String& rpsPrefix) |
| { |
| Status s = CheckIfIsInitialized(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| m_RPAServer = backend; |
| m_RPAServer.TrimRight("/"); |
| s = GetClientSettings(m_RPAServer, rpsPrefix, &m_clientSettings); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| s = RewriteRelativeUrls(); |
| |
| // Check to see if the old access number algorithm is used and if yes, disable check sum validation |
| if(m_clientSettings.GetIntParam("cSum", 0) == 0) |
| { |
| m_clientSettings["accessNumberUseCheckSum"] = json::Boolean(false); |
| } |
| |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| s = LoadUsersFromStorage(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| m_state = BACKEND_SET; |
| return Status(Status::OK); |
| } |
| |
| UserPtr MPinSDK::MakeNewUser(const String& id, const String& deviceName) const |
| { |
| return UserPtr(new User(id, deviceName)); |
| } |
| |
| Status MPinSDK::StartRegistration(UserPtr user, const String& activateCode, const String& userData) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| s = CheckUserState(user, User::INVALID); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| return RequestRegistration(user, activateCode, userData); |
| } |
| |
| Status MPinSDK::RestartRegistration(UserPtr user, const String& userData) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| s = CheckUserState(user, User::STARTED_REGISTRATION); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| return RequestRegistration(user, "", userData); |
| } |
| |
| Status MPinSDK::RequestRegistration(UserPtr user, const String& activateCode, const String& userData) |
| { |
| // Make request to RPA to add M-Pin ID |
| util::JsonObject data; |
| data["userId"] = json::String(user->GetId()); |
| data["mobile"] = json::Number(1); |
| if(!user->GetDeviceName().empty()) |
| { |
| data["deviceName"] = json::String(user->GetDeviceName()); |
| } |
| if(!userData.empty()) |
| { |
| data["userData"] = json::String(userData); |
| } |
| if(!activateCode.empty()) |
| { |
| data["activateCode"] = json::String(activateCode); |
| } |
| |
| String url; |
| if(user->GetState() == User::STARTED_REGISTRATION) |
| { |
| data["regOTT"] = json::String(user->GetRegOTT()); |
| url.Format("%s/%s", m_clientSettings.GetStringParam("registerURL"), user->GetMPinIdHex().c_str()); |
| } |
| else |
| { |
| url = m_clientSettings.GetStringParam("registerURL"); |
| } |
| |
| HttpResponse response = MakeRequest(url, IHttpRequest::PUT, data); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::REGISTER); |
| } |
| |
| bool writeUsersToStorage = false; |
| |
| bool userIsNew = (user->GetState() == User::INVALID); |
| if(userIsNew) |
| { |
| AddUser(user); |
| } |
| |
| String mpinIdHex = response.GetJsonData().GetStringParam("mpinId"); |
| String regOTT = response.GetJsonData().GetStringParam("regOTT"); |
| bool userDataChanged = (regOTT != user->GetRegOTT() || mpinIdHex != user->GetMPinIdHex()); |
| |
| if(userIsNew || userDataChanged) |
| { |
| user->SetStartedRegistration(mpinIdHex, regOTT); |
| writeUsersToStorage = true; |
| } |
| |
| if(response.GetJsonData().GetBoolParam("active")) |
| { |
| user->SetActivated(); |
| writeUsersToStorage = true; |
| } |
| |
| if(writeUsersToStorage) |
| { |
| Status s = WriteUsersToStorage(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| Status MPinSDK::ConfirmRegistration(INOUT UserPtr user, const String& pushMessageIdentifier) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // A user can get here either in STARTED_REGISTRATION state or in ACTIVATED state (force-activate flow) |
| // In the first case, the method might fail if the user identity has not been verified yet, and the user state |
| // should stay as it was - STARTED_REGISTRATION |
| s = CheckUserState(user, User::STARTED_REGISTRATION); |
| if(s != Status::OK) |
| { |
| Status sSave = s; |
| s = CheckUserState(user, User::ACTIVATED); |
| if ( s != Status::OK ) |
| { |
| return sSave; |
| } |
| } |
| |
| // Request a client secret share from the customer's D-TA and a signed request for a client secret share from CertiVox's D-TA. |
| String mpinId = user->GetMPinId(); |
| String mpinIdHex = user->GetMPinIdHex(); |
| String regOTT = user->GetRegOTT(); |
| |
| String url = String().Format("%s/%s?regOTT=%s", m_clientSettings.GetStringParam("signatureURL"), mpinIdHex.c_str(), regOTT.c_str()); |
| if(!pushMessageIdentifier.empty()) |
| { |
| url += "&pmiToken=" + pushMessageIdentifier; |
| } |
| |
| HttpResponse response = MakeGetRequest(url); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::GET_CLIENT_SECRET1); |
| } |
| |
| user->m_clientSecret1 = util::HexDecode(response.GetJsonData().GetStringParam("clientSecretShare")); |
| |
| // Request the client secret share from CertiVox's D-TA. |
| String cs2Params = response.GetJsonData().GetStringParam("params"); |
| url.Format("%sclientSecret?%s", m_clientSettings.GetStringParam("certivoxURL"), cs2Params.c_str()); |
| response = MakeGetRequest(url); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::GET_CLIENT_SECRET2); |
| } |
| |
| user->m_clientSecret2 = util::HexDecode(response.GetJsonData().GetStringParam("clientSecret")); |
| |
| return Status::OK; |
| } |
| |
| Status MPinSDK::FinishRegistration(INOUT UserPtr user, const String& pin) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // A user can get here either in STARTED_REGISTRATION state or in ACTIVATED state (force-activate flow) |
| // In the first case, the method might fail if the user identity has not been verified yet, and the user state |
| // should stay as it was - STARTED_REGISTRATION |
| s = CheckUserState(user, User::STARTED_REGISTRATION); |
| if(s != Status::OK) |
| { |
| Status sSave = s; |
| s = CheckUserState(user, User::ACTIVATED); |
| if ( s != Status::OK ) |
| { |
| return sSave; |
| } |
| } |
| |
| // In addition, client secret shares must be retrieved |
| if(user->m_clientSecret1.empty() || user->m_clientSecret2.empty()) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("Cannot finish user '%s' registration: User identity not verified", user->GetId().c_str())); |
| } |
| |
| s = m_crypto->OpenSession(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| std::vector<String> clientSecretShares; |
| clientSecretShares.push_back(user->m_clientSecret1); |
| clientSecretShares.push_back(user->m_clientSecret2); |
| |
| s = m_crypto->Register(user, pin, clientSecretShares); |
| if(s != Status::OK) |
| { |
| m_crypto->CloseSession(); |
| return s; |
| } |
| |
| m_crypto->CloseSession(); |
| |
| user->SetRegistered(); |
| s = WriteUsersToStorage(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| return Status::OK; |
| } |
| |
| Status MPinSDK::StartAuthentication(INOUT UserPtr user) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // Check if the user is already registered |
| s = CheckUserState(user, User::REGISTERED); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // Request a time permit share from the customer's D-TA and a signed request for a time permit share from CertiVox's D-TA. |
| String mpinIdHex = user->GetMPinIdHex(); |
| String url = String().Format("%s/%s", m_clientSettings.GetStringParam("timePermitsURL"), mpinIdHex.c_str()); |
| HttpResponse response = MakeGetRequest(url); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::GET_TIME_PERMIT1); |
| } |
| |
| user->m_timePermitShare1 = util::HexDecode(response.GetJsonData().GetStringParam("timePermit")); |
| |
| // Request time permit share from CertiVox's D-TA (Searches first in user cache, than in S3 cache) |
| s = GetCertivoxTimePermitShare(user, response.GetJsonData(), user->m_timePermitShare2); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| return Status::OK; |
| } |
| |
| Status MPinSDK::CheckAccessNumber(const String& accessNumber) |
| { |
| if(accessNumber.empty() || !ValidateAccessNumber(accessNumber)) |
| { |
| return Status(Status::INCORRECT_ACCESS_NUMBER, "Invalid access number"); |
| } |
| |
| return Status::OK; |
| } |
| |
| Status MPinSDK::FinishAuthentication(INOUT UserPtr user, const String& pin) |
| { |
| util::JsonObject authResult; |
| return FinishAuthenticationImpl(user, pin, "", NULL, authResult); |
| } |
| |
| Status MPinSDK::FinishAuthentication(INOUT UserPtr user, const String& pin, OUT String& authResultData) |
| { |
| util::JsonObject authResult; |
| |
| Status s = FinishAuthenticationImpl(user, pin, "", NULL, authResult); |
| |
| authResultData = authResult.ToString(); |
| return s; |
| } |
| |
| Status MPinSDK::FinishAuthenticationOTP(INOUT UserPtr user, const String& pin, OUT OTP& otp) |
| { |
| util::JsonObject authResult; |
| String otpNumber; |
| |
| Status s = FinishAuthenticationImpl(user, pin, "", &otpNumber, authResult); |
| |
| otp.ExtractFrom(otpNumber, authResult); |
| return s; |
| } |
| |
| Status MPinSDK::FinishAuthenticationAN(INOUT UserPtr user, const String& pin, const String& accessNumber) |
| { |
| util::JsonObject authResult; |
| |
| Status s = FinishAuthenticationImpl(user, pin, accessNumber, NULL, authResult); |
| |
| LogoutData logoutData; |
| if(logoutData.ExtractFrom(authResult)) |
| { |
| m_logoutData.insert(std::make_pair(user, logoutData)); |
| } |
| |
| return s; |
| } |
| |
| Status MPinSDK::FinishAuthenticationImpl(INOUT UserPtr user, const String& pin, const String& accessNumber, OUT String *otp, OUT util::JsonObject& authResultData) |
| { |
| Status s = CheckIfBackendIsSet(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // Check if the user is already registered |
| s = CheckUserState(user, User::REGISTERED); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| // Check if time permit was obtained from StartAuthentication |
| if(user->m_timePermitShare1.empty() || user->m_timePermitShare2.empty()) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("Cannot finish user '%s' authentication: Invalid time permit", user->GetId().c_str())); |
| } |
| |
| String mpinIdHex = user->GetMPinIdHex(); |
| |
| s = m_crypto->OpenSession(); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| std::vector<String> timePermitShares; |
| timePermitShares.push_back(user->m_timePermitShare1); |
| timePermitShares.push_back(user->m_timePermitShare2); |
| |
| int date = user->GetTimePermitCache().GetDate(); |
| |
| // Authentication pass 1 |
| String u, ut; |
| s = m_crypto->AuthenticatePass1(user, pin, date, timePermitShares, u, ut); |
| if(s != Status::OK) |
| { |
| m_crypto->CloseSession(); |
| return s; |
| } |
| |
| util::JsonObject requestData; |
| requestData["pass"] = json::Number(1); |
| requestData["mpin_id"] = json::String(mpinIdHex); |
| requestData["UT"] = json::String(util::HexEncode(ut)); |
| requestData["U"] = json::String(util::HexEncode(u)); |
| |
| String mpinAuthServerURL = m_clientSettings.GetStringParam("mpinAuthServerURL"); |
| String url = String().Format("%s/pass1", mpinAuthServerURL.c_str()); |
| HttpResponse response = MakeRequest(url, IHttpRequest::POST, requestData); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| m_crypto->CloseSession(); |
| return response.TranslateToMPinStatus(HttpResponse::AUTHENTICATE_PASS1); |
| } |
| |
| String y = util::HexDecode(response.GetJsonData().GetStringParam("y")); |
| |
| // Authentication pass 2 |
| String v; |
| m_crypto->AuthenticatePass2(user, y, v); |
| if(s != Status::OK) |
| { |
| m_crypto->CloseSession(); |
| return s; |
| } |
| |
| requestData.Clear(); |
| requestData["pass"] = json::Number(2); |
| requestData["OTP"] = json::Boolean(otp != NULL ? true : false); |
| requestData["WID"] = json::String(accessNumber.empty() ? "0" : accessNumber); |
| requestData["V"] = json::String(util::HexEncode(v)); |
| requestData["mpin_id"] = json::String(mpinIdHex); |
| |
| url.Format("%s/pass2", mpinAuthServerURL.c_str()); |
| response = MakeRequest(url, IHttpRequest::POST, requestData); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| m_crypto->CloseSession(); |
| return response.TranslateToMPinStatus(HttpResponse::AUTHENTICATE_PASS2); |
| } |
| |
| // Save OTP data to be used if otp was requested |
| if(otp != NULL) |
| { |
| *otp = response.GetJsonData().GetStringParam("OTP"); |
| } |
| |
| // Send response data from M-Pin authentication server to RPA |
| url = m_clientSettings.GetStringParam(accessNumber.empty() ? "authenticateURL" : "mobileAuthenticateURL"); |
| requestData.Clear(); |
| requestData["mpinResponse"] = response.GetJsonData(); |
| response = MakeRequest(url, IHttpRequest::POST, requestData); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| m_crypto->CloseSession(); |
| s = response.TranslateToMPinStatus(HttpResponse::AUTHENTICATE_RPA); |
| |
| if(response.GetStatus() == HttpResponse::HTTP_GONE) |
| { |
| user->Block(); |
| m_crypto->DeleteToken(user->GetMPinId()); |
| WriteUsersToStorage(); |
| } |
| |
| return s; |
| } |
| |
| // You are now logged in with M-Pin! |
| m_crypto->CloseSession(); |
| |
| authResultData = response.GetJsonData(); |
| |
| return Status::OK; |
| } |
| |
| Status MPinSDK::GetCertivoxTimePermitShare(INOUT UserPtr user, const util::JsonObject& cutomerTimePermitData, OUT String& resultTimePermit) |
| { |
| // First check if we have cached time permit in user and if it is still valid (for today) |
| int date = cutomerTimePermitData.GetIntParam("date"); |
| const TimePermitCache& userCache = user->GetTimePermitCache(); |
| const String& cachedTimePermit = userCache.GetTimePermit(); |
| if(!cachedTimePermit.empty() && userCache.GetDate() == date) |
| { |
| resultTimePermit = cachedTimePermit; |
| return Status(Status::OK); |
| } |
| |
| // No or too old cached time permit - try get time permit from S3 |
| String s3Url = m_clientSettings.GetStringParam("timePermitsStorageURL"); |
| String appId = m_clientSettings.GetStringParam("appID"); |
| String storageId = cutomerTimePermitData.GetStringParam("storageId"); |
| |
| // Make GET request to s3Url/app_id/date/storageId |
| String url = String().Format("%s/%s/%d/%s", s3Url.c_str(), appId.c_str(), date, storageId.c_str()); |
| HttpResponse response = MakeGetRequest(url, HttpResponse::RAW); |
| if(response.GetStatus() == HttpResponse::HTTP_OK) |
| { |
| // OK - add time permit to user cache |
| resultTimePermit = util::HexDecode(response.GetRawData()); |
| user->CacheTimePermit(resultTimePermit, date); |
| WriteUsersToStorage(); |
| return Status(Status::OK); |
| } |
| |
| // No cached time permit in S3 or something other went wrong |
| // Finally request time permit share from CertiVox's D-TA |
| String signature = cutomerTimePermitData.GetStringParam("signature"); |
| String t2Params = String().Format("hash_mpin_id=%s&app_id=%s&mobile=1&signature=%s", |
| storageId.c_str(), appId.c_str(), signature.c_str()); |
| url.Format("%stimePermit?%s", m_clientSettings.GetStringParam("certivoxURL"), t2Params.c_str()); |
| response = MakeGetRequest(url); |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return response.TranslateToMPinStatus(HttpResponse::GET_TIME_PERMIT2); |
| } |
| |
| resultTimePermit = util::HexDecode(response.GetJsonData().GetStringParam("timePermit")); |
| // OK - add time permit to user cache |
| user->CacheTimePermit(resultTimePermit, date); |
| WriteUsersToStorage(); |
| |
| return Status(Status::OK); |
| } |
| |
| void MPinSDK::OTP::ExtractFrom(const String& otpData, const util::JsonObject& json) |
| { |
| if(otpData.empty()) |
| { |
| status = Status(Status::RESPONSE_PARSE_ERROR, "OTP not issued"); |
| return; |
| } |
| |
| otp = otpData; |
| |
| ttlSeconds = json.GetIntParam("ttlSeconds"); |
| int64_t tmp = json.GetInt64Param("expireTime"); |
| expireTime = (long)(tmp / 1000); |
| tmp = json.GetInt64Param("nowTime"); |
| nowTime = (long)(tmp / 1000); |
| |
| if(expireTime == 0 || ttlSeconds == 0 || nowTime == 0) |
| { |
| status = Status(Status::RESPONSE_PARSE_ERROR, "OTP data is malformed"); |
| } |
| } |
| |
| bool MPinSDK::LogoutData::ExtractFrom(const util::JsonObject& json) |
| { |
| logoutURL = json.GetStringParam("logoutURL"); |
| json::Object::const_iterator i = json.Find("logoutData"); |
| |
| if(i == json.End()) |
| { |
| return false; |
| } |
| |
| try |
| { |
| logoutData = util::JsonObject(i->element).ToString(); |
| } |
| catch(json::Exception&) |
| { |
| logoutData = ""; |
| } |
| |
| return true; |
| } |
| |
| bool MPinSDK::ValidateAccessNumber(const String& accessNumber) |
| { |
| bool accessNumberUseCheckSum = m_clientSettings.GetBoolParam("accessNumberUseCheckSum", true); |
| int accessNumberDigits = m_clientSettings.GetIntParam("accessNumberDigits", AN_WITH_CHECKSUM_LEN); |
| |
| if(!accessNumberUseCheckSum || accessNumberDigits != AN_WITH_CHECKSUM_LEN) |
| { |
| return true; |
| } |
| |
| return ValidateAccessNumberChecksum(accessNumber); |
| } |
| |
| bool MPinSDK::ValidateAccessNumberChecksum(const String& accessNumber) |
| { |
| size_t len = accessNumber.length(); |
| const char *data = accessNumber.data(); |
| // Checksum is the last number digit (0 - 9) |
| int checkSum = data[len - 1]; |
| // We are working with AN_WITH_CHECKSUM_LEN *digit* access numbers only |
| if(len != AN_WITH_CHECKSUM_LEN || !isdigit(checkSum)) |
| { |
| return false; |
| } |
| checkSum -= '0'; |
| |
| int calculatedCheckSum = 0; |
| for( ; len > 1; --len, ++data) |
| { |
| if(!isdigit(*data)) |
| { |
| return false; |
| } |
| |
| calculatedCheckSum += (*data - '0') * len; |
| } |
| |
| calculatedCheckSum = ((11 - calculatedCheckSum % 11) % 11) % 10; |
| return calculatedCheckSum == checkSum; |
| } |
| |
| void MPinSDK::DeleteUser(UserPtr user) |
| { |
| UsersMap::iterator i = m_users.find(user->GetId()); |
| if(i == m_users.end() || user != i->second) |
| { |
| return; |
| } |
| |
| m_crypto->DeleteRegOTT(i->second->GetMPinId()); |
| m_crypto->DeleteToken(i->second->GetMPinId()); |
| i->second->Invalidate(); |
| m_users.erase(i); |
| WriteUsersToStorage(); |
| m_logoutData.erase(user); |
| } |
| |
| void MPinSDK::ListUsers(std::vector<UserPtr>& users) const |
| { |
| ListUsers(users, m_users); |
| } |
| |
| void MPinSDK::ListUsers(OUT std::vector<UserPtr>& users, const String& backend) const |
| { |
| UsersMap usersMap; |
| LoadUsersFromStorage(backend, usersMap); |
| ListUsers(users, usersMap); |
| } |
| |
| void MPinSDK::ListUsers(OUT std::vector<UserPtr>& users, const UsersMap& usersMap) const |
| { |
| users.clear(); |
| users.reserve(usersMap.size()); |
| for(UsersMap::const_iterator i = usersMap.begin(); i != usersMap.end(); ++i) |
| { |
| users.push_back(i->second); |
| } |
| } |
| |
| void MPinSDK::AddUser(UserPtr user) |
| { |
| m_users[user->GetId()] = user; |
| } |
| |
| Status MPinSDK::CheckUserState(UserPtr user, User::State expectedState) |
| { |
| UsersMap::iterator i = m_users.find(user->GetId()); |
| if(expectedState == User::INVALID) |
| { |
| if(i != m_users.end()) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("User '%s' was already added", user->GetId().c_str())); |
| } |
| |
| if(user->GetState() != User::INVALID) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("Invalid '%s' user state: current state=%s, expected state=%s", |
| user->GetId().c_str(), User::StateToString( user->GetState() ).c_str(), User::StateToString( expectedState ).c_str())); |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| if(i == m_users.end()) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("User '%s' was not added or has been deleted", user->GetId().c_str())); |
| } |
| |
| if(user != i->second) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("Different user with the '%s' id was previously added", user->GetId().c_str())); |
| } |
| |
| if(user->GetState() != expectedState) |
| { |
| return Status(Status::FLOW_ERROR, String().Format("Invalid '%s' user state: current state=%s, expected state=%s", |
| user->GetId().c_str(), User::StateToString( user->GetState() ).c_str(), User::StateToString( expectedState ).c_str())); |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| Status MPinSDK::WriteUsersToStorage() |
| { |
| IStorage* storage = m_context->GetStorage(IStorage::NONSECURE); |
| String data; |
| storage->GetData(data); |
| data.Trim(); |
| |
| try |
| { |
| json::Object allBackendsObject; |
| if(!data.empty()) |
| { |
| std::istringstream strIn(data); |
| json::Reader::Read(allBackendsObject, strIn); |
| } |
| |
| String backend = m_RPAServer; |
| backend.ReplaceAll("https://", ""); |
| backend.ReplaceAll("http://", ""); |
| |
| json::Object& rootObject = (json::Object&) allBackendsObject[backend]; |
| rootObject.Clear(); |
| |
| for (UsersMap::iterator i = m_users.begin(); i != m_users.end(); ++i) |
| { |
| UserPtr user = i->second; |
| |
| json::Object timePermitCacheObject; |
| timePermitCacheObject["date"] = json::Number(user->GetTimePermitCache().GetDate()); |
| timePermitCacheObject["timePermit"] = json::String(util::HexEncode(user->GetTimePermitCache().GetTimePermit())); |
| |
| json::Object userObject; |
| userObject["timePermitCache"] = timePermitCacheObject; |
| |
| if(!user->GetDeviceName().empty()) |
| { |
| userObject["deviceName"] = json::String(user->GetDeviceName()); |
| } |
| |
| userObject["state"] = json::String(user->GetStateString()); |
| |
| rootObject[user->GetMPinIdHex()] = userObject; |
| |
| Status s; |
| switch(user->GetState()) |
| { |
| case User::STARTED_REGISTRATION: |
| case User::ACTIVATED: |
| s = m_crypto->SaveRegOTT(user->GetMPinId(), user->GetRegOTT()); |
| break; |
| case User::REGISTERED: |
| s = m_crypto->DeleteRegOTT(user->GetMPinId()); |
| break; |
| default: |
| break; |
| } |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| } |
| |
| std::stringstream strOut; |
| json::Writer::Write(allBackendsObject, strOut); |
| storage->SetData(strOut.str()); |
| |
| } |
| catch(const json::Exception& e) |
| { |
| return Status(Status::STORAGE_ERROR, e.what()); |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| Status MPinSDK::LoadUsersFromStorage() |
| { |
| ClearUsers(); |
| return LoadUsersFromStorage(m_RPAServer, m_users); |
| } |
| |
| Status MPinSDK::LoadUsersFromStorage(const String& backendServer, UsersMap& usersMap) const |
| { |
| IStorage* storage = m_context->GetStorage(IStorage::NONSECURE); |
| String data; |
| storage->GetData(data); |
| data.Trim(); |
| if(data.empty()) |
| { |
| return Status(Status::OK); |
| } |
| |
| try |
| { |
| json::Object allBackendsObject; |
| std::istringstream str(data); |
| json::Reader::Read(allBackendsObject, str); |
| |
| String backend = backendServer; |
| backend.ReplaceAll("https://", ""); |
| backend.ReplaceAll("http://", ""); |
| |
| json::Object::const_iterator i = allBackendsObject.Find(backend); |
| if(i == allBackendsObject.End()) |
| { |
| return Status(Status::OK); |
| } |
| |
| const json::Object& rootObject = (const json::Object&) i->element; |
| for(i = rootObject.Begin(); i != rootObject.End(); ++i) |
| { |
| const String& mpinIdHex = i->name; |
| String mpinId = util::HexDecode(mpinIdHex); |
| util::JsonObject mpinIdJson; |
| if(!mpinIdJson.Parse(mpinId.c_str())) |
| { |
| return Status(Status::STORAGE_ERROR, String().Format("Failed to parse mpinId json: '%s'", mpinId.c_str())); |
| } |
| const json::Object& userObject = (const json::Object&) i->element; |
| const std::string& id = ((const json::String&) mpinIdJson["userID"]).Value(); |
| std::string deviceName; |
| json::Object::const_iterator dni = userObject.Find("deviceName"); |
| if(dni != userObject.End()) |
| { |
| deviceName = ((const json::String&) dni->element).Value(); |
| } |
| |
| String regOTT; |
| Status s = m_crypto->LoadRegOTT(mpinId, regOTT); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| UserPtr user = MakeNewUser(id, deviceName); |
| s = user->RestoreState(((const json::String&) userObject["state"]).Value(), mpinIdHex, regOTT); |
| if(s != Status::OK) |
| { |
| return s; |
| } |
| |
| const json::Object& timePermitCacheObject = (const json::Object&) userObject["timePermitCache"]; |
| int date = (int) ((const json::Number&) timePermitCacheObject["date"]).Value(); |
| const String& timePermit = util::HexDecode(((const json::String&) timePermitCacheObject["timePermit"]).Value()); |
| |
| user->CacheTimePermit(timePermit, date); |
| |
| usersMap[id] = user; |
| } |
| } |
| catch(const json::Exception& e) |
| { |
| return Status(Status::STORAGE_ERROR, e.what()); |
| } |
| |
| return Status(Status::OK); |
| } |
| |
| void MPinSDK::ListBackends(OUT std::vector<String>& backends) const |
| { |
| IStorage* storage = m_context->GetStorage(IStorage::NONSECURE); |
| String data; |
| storage->GetData(data); |
| data.Trim(); |
| if(data.empty()) |
| { |
| return; |
| } |
| |
| try |
| { |
| json::Object allBackendsObject; |
| std::istringstream str(data); |
| json::Reader::Read(allBackendsObject, str); |
| |
| for(json::Object::const_iterator i = allBackendsObject.Begin(); i != allBackendsObject.End(); ++i) |
| { |
| backends.push_back(i->name); |
| } |
| } |
| catch(const json::Exception&) {} |
| } |
| |
| const char * MPinSDK::GetVersion() |
| { |
| return MPIN_SDK_V2_VERSION; |
| } |
| |
| bool MPinSDK::CanLogout(UserPtr user) |
| { |
| LogoutDataMap::iterator i = m_logoutData.find(user); |
| if (i == m_logoutData.end()) return false; |
| if (i->second.logoutURL.empty()) return false; |
| return true; |
| } |
| |
| bool MPinSDK::Logout(UserPtr user) |
| { |
| LogoutDataMap::iterator i = m_logoutData.find(user); |
| if (i == m_logoutData.end()) return false; |
| if (i->second.logoutURL.empty()) return false; |
| util::JsonObject logoutData; |
| if (!logoutData.Parse(i->second.logoutData.c_str())) |
| { |
| return false; |
| } |
| |
| String url = String().Format("%s%s", m_RPAServer.c_str(), i->second.logoutURL.c_str()); |
| HttpResponse response = MakeRequest(url, IHttpRequest::POST, logoutData); |
| |
| if(response.GetStatus() != HttpResponse::HTTP_OK) |
| { |
| return false; |
| } |
| m_logoutData.erase(i); |
| return true; |
| } |
| |
| class StringVisitor:public json::Visitor |
| { |
| public: |
| virtual ~StringVisitor() {} |
| |
| virtual void Visit(json::Array& array) {} |
| virtual void Visit(json::Object& object) {} |
| virtual void Visit(json::Null& null){} |
| |
| virtual void Visit(json::Number& number) |
| { |
| data << (int) number.Value(); |
| } |
| |
| virtual void Visit(json::String& string) |
| { |
| data << string.Value(); |
| } |
| |
| virtual void Visit(json::Boolean& boolean) |
| { |
| data << (boolean.Value() ? "true" : "false"); |
| } |
| |
| String GetData() |
| { |
| return data.str(); |
| } |
| |
| private: |
| std::stringstream data; |
| }; |
| |
| String MPinSDK::GetClientParam(const String& key) |
| { |
| StringVisitor sv; |
| m_clientSettings[key].Accept(sv); |
| return sv.GetData(); |
| } |