| // Copyright 2015 The Weave Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/privet/privet_handler.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/location.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/values.h> |
| #include <weave/enum_to_string.h> |
| #include <weave/provider/task_runner.h> |
| |
| #include "src/http_constants.h" |
| #include "src/privet/cloud_delegate.h" |
| #include "src/privet/constants.h" |
| #include "src/privet/device_delegate.h" |
| #include "src/privet/device_ui_kind.h" |
| #include "src/privet/security_delegate.h" |
| #include "src/privet/wifi_delegate.h" |
| #include "src/string_utils.h" |
| #include "src/utils.h" |
| |
| namespace weave { |
| namespace privet { |
| |
| namespace { |
| |
| const char kInfoVersionKey[] = "version"; |
| const char kInfoVersionValue[] = "3.0"; |
| |
| const char kNameKey[] = "name"; |
| const char kDescrptionKey[] = "description"; |
| const char kLocationKey[] = "location"; |
| |
| const char kGcdKey[] = "gcd"; |
| const char kWifiKey[] = "wifi"; |
| const char kStatusKey[] = "status"; |
| const char kErrorKey[] = "error"; |
| const char kCryptoKey[] = "crypto"; |
| const char kStatusErrorValue[] = "error"; |
| |
| const char kInfoIdKey[] = "id"; |
| const char kInfoServicesKey[] = "services"; |
| |
| const char kInfoEndpointsKey[] = "endpoints"; |
| const char kInfoEndpointsHttpPortKey[] = "httpPort"; |
| const char kInfoEndpointsHttpUpdatePortKey[] = "httpUpdatesPort"; |
| const char kInfoEndpointsHttpsPortKey[] = "httpsPort"; |
| const char kInfoEndpointsHttpsUpdatePortKey[] = "httpsUpdatesPort"; |
| |
| const char kInfoModelIdKey[] = "modelManifestId"; |
| const char kInfoModelManifestKey[] = "basicModelManifest"; |
| const char kInfoManifestUiDeviceKind[] = "uiDeviceKind"; |
| const char kInfoManifestOemName[] = "oemName"; |
| const char kInfoManifestModelName[] = "modelName"; |
| |
| const char kInfoAuthenticationKey[] = "authentication"; |
| |
| const char kInfoAuthAnonymousMaxScopeKey[] = "anonymousMaxScope"; |
| |
| const char kInfoWifiCapabilitiesKey[] = "capabilities"; |
| const char kInfoWifiSsidKey[] = "ssid"; |
| const char kInfoWifiHostedSsidKey[] = "hostedSsid"; |
| |
| const char kInfoUptimeKey[] = "uptime"; |
| |
| const char kPairingKey[] = "pairing"; |
| const char kPairingSessionIdKey[] = "sessionId"; |
| const char kPairingDeviceCommitmentKey[] = "deviceCommitment"; |
| const char kPairingClientCommitmentKey[] = "clientCommitment"; |
| const char kPairingFingerprintKey[] = "certFingerprint"; |
| const char kPairingSignatureKey[] = "certSignature"; |
| |
| const char kAuthTypeAnonymousValue[] = "anonymous"; |
| const char kAuthTypePairingValue[] = "pairing"; |
| |
| const char kAuthModeKey[] = "mode"; |
| const char kAuthCodeKey[] = "authCode"; |
| const char kAuthRequestedScopeKey[] = "requestedScope"; |
| const char kAuthScopeAutoValue[] = "auto"; |
| |
| const char kAuthAccessTokenKey[] = "accessToken"; |
| const char kAuthTokenTypeKey[] = "tokenType"; |
| const char kAuthExpiresInKey[] = "expiresIn"; |
| const char kAuthScopeKey[] = "scope"; |
| |
| const char kAuthorizationHeaderPrefix[] = "Privet"; |
| |
| const char kErrorDebugInfoKey[] = "debugInfo"; |
| |
| const char kSetupStartSsidKey[] = "ssid"; |
| const char kSetupStartPassKey[] = "passphrase"; |
| const char kSetupStartTicketIdKey[] = "ticketId"; |
| const char kSetupStartUserKey[] = "user"; |
| |
| const char kFingerprintKey[] = "fingerprint"; |
| const char kStateKey[] = "state"; |
| const char kCommandsKey[] = "commands"; |
| const char kCommandsIdKey[] = "id"; |
| |
| const char kStateFingerprintKey[] = "stateFingerprint"; |
| const char kCommandsFingerprintKey[] = "commandsFingerprint"; |
| const char kWaitTimeoutKey[] = "waitTimeout"; |
| |
| const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'"; |
| |
| const int kAccessTokenExpirationSeconds = 3600; |
| |
| // Threshold to reduce probability of expiration because of clock difference |
| // between device and client. Value is just a guess. |
| const int kAccessTokenExpirationThresholdSeconds = 300; |
| |
| template <class Container> |
| std::unique_ptr<base::ListValue> ToValue(const Container& list) { |
| std::unique_ptr<base::ListValue> value_list(new base::ListValue()); |
| for (const std::string& val : list) |
| value_list->AppendString(val); |
| return value_list; |
| } |
| |
| struct { |
| const char* const reason; |
| int code; |
| } kReasonToCode[] = { |
| {errors::kInvalidClientCommitment, http::kForbidden}, |
| {errors::kInvalidFormat, http::kBadRequest}, |
| {errors::kMissingAuthorization, http::kDenied}, |
| {errors::kInvalidAuthorization, http::kDenied}, |
| {errors::kInvalidAuthorizationScope, http::kForbidden}, |
| {errors::kAuthorizationExpired, http::kForbidden}, |
| {errors::kCommitmentMismatch, http::kForbidden}, |
| {errors::kUnknownSession, http::kNotFound}, |
| {errors::kInvalidAuthCode, http::kForbidden}, |
| {errors::kInvalidAuthMode, http::kBadRequest}, |
| {errors::kInvalidRequestedScope, http::kBadRequest}, |
| {errors::kAccessDenied, http::kForbidden}, |
| {errors::kInvalidParams, http::kBadRequest}, |
| {errors::kSetupUnavailable, http::kBadRequest}, |
| {errors::kDeviceBusy, http::kServiceUnavailable}, |
| {errors::kInvalidState, http::kInternalServerError}, |
| {errors::kNotFound, http::kNotFound}, |
| {errors::kNotImplemented, http::kNotSupported}, |
| }; |
| |
| AuthScope AuthScopeFromString(const std::string& scope, AuthScope auto_scope) { |
| if (scope == kAuthScopeAutoValue) |
| return auto_scope; |
| AuthScope scope_id = AuthScope::kNone; |
| StringToEnum(scope, &scope_id); |
| return scope_id; |
| } |
| |
| std::string GetAuthTokenFromAuthHeader(const std::string& auth_header) { |
| return SplitAtFirst(auth_header, " ", true).second; |
| } |
| |
| // Creates JSON similar to GCD server error format. |
| std::unique_ptr<base::DictionaryValue> ErrorToJson(const Error& error) { |
| std::unique_ptr<base::DictionaryValue> output{ErrorInfoToJson(error)}; |
| |
| // Optional debug information. |
| std::unique_ptr<base::ListValue> errors{new base::ListValue}; |
| for (const Error* it = &error; it; it = it->GetInnerError()) { |
| std::unique_ptr<base::DictionaryValue> inner{ErrorInfoToJson(*it)}; |
| tracked_objects::Location location{it->GetLocation().function_name.c_str(), |
| it->GetLocation().file_name.c_str(), |
| it->GetLocation().line_number, nullptr}; |
| inner->SetString(kErrorDebugInfoKey, location.ToString()); |
| errors->Append(inner.release()); |
| } |
| output->Set(kErrorDebugInfoKey, errors.release()); |
| return output; |
| } |
| |
| template <class T> |
| void SetStateProperties(const T& state, base::DictionaryValue* parent) { |
| if (!state.error()) { |
| parent->SetString(kStatusKey, EnumToString(state.status())); |
| return; |
| } |
| parent->SetString(kStatusKey, kStatusErrorValue); |
| parent->Set(kErrorKey, ErrorToJson(*state.error()).release()); |
| } |
| |
| void ReturnError(const Error& error, |
| const PrivetHandler::RequestCallback& callback) { |
| int code = http::kInternalServerError; |
| for (const auto& it : kReasonToCode) { |
| if (error.HasError(errors::kDomain, it.reason)) { |
| code = it.code; |
| break; |
| } |
| } |
| std::unique_ptr<base::DictionaryValue> output{new base::DictionaryValue}; |
| output->Set(kErrorKey, ErrorToJson(error).release()); |
| callback.Run(code, *output); |
| } |
| |
| void OnCommandRequestSucceeded(const PrivetHandler::RequestCallback& callback, |
| const base::DictionaryValue& output, |
| ErrorPtr error) { |
| if (!error) |
| return callback.Run(http::kOk, output); |
| |
| if (error->HasError("gcd", "unknown_command")) { |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kNotFound, |
| "Unknown command ID"); |
| return ReturnError(*error, callback); |
| } |
| if (error->HasError("gcd", "access_denied")) { |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kAccessDenied, |
| error->GetMessage()); |
| return ReturnError(*error, callback); |
| } |
| return ReturnError(*error, callback); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CreateManifestSection( |
| const CloudDelegate& cloud) { |
| std::unique_ptr<base::DictionaryValue> manifest(new base::DictionaryValue()); |
| manifest->SetString(kInfoManifestUiDeviceKind, |
| GetDeviceUiKind(cloud.GetModelId())); |
| manifest->SetString(kInfoManifestOemName, cloud.GetOemName()); |
| manifest->SetString(kInfoManifestModelName, cloud.GetModelName()); |
| return manifest; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CreateEndpointsSection( |
| const DeviceDelegate& device) { |
| std::unique_ptr<base::DictionaryValue> endpoints(new base::DictionaryValue()); |
| auto http_endpoint = device.GetHttpEnpoint(); |
| endpoints->SetInteger(kInfoEndpointsHttpPortKey, http_endpoint.first); |
| endpoints->SetInteger(kInfoEndpointsHttpUpdatePortKey, http_endpoint.second); |
| |
| auto https_endpoint = device.GetHttpsEnpoint(); |
| endpoints->SetInteger(kInfoEndpointsHttpsPortKey, https_endpoint.first); |
| endpoints->SetInteger(kInfoEndpointsHttpsUpdatePortKey, |
| https_endpoint.second); |
| |
| return endpoints; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CreateInfoAuthSection( |
| const SecurityDelegate& security, |
| AuthScope anonymous_max_scope) { |
| std::unique_ptr<base::DictionaryValue> auth(new base::DictionaryValue()); |
| |
| auth->SetString(kInfoAuthAnonymousMaxScopeKey, |
| EnumToString(anonymous_max_scope)); |
| |
| std::unique_ptr<base::ListValue> pairing_types(new base::ListValue()); |
| for (PairingType type : security.GetPairingTypes()) |
| pairing_types->AppendString(EnumToString(type)); |
| auth->Set(kPairingKey, pairing_types.release()); |
| |
| std::unique_ptr<base::ListValue> auth_types(new base::ListValue()); |
| auth_types->AppendString(kAuthTypeAnonymousValue); |
| auth_types->AppendString(kAuthTypePairingValue); |
| |
| // TODO(vitalybuka): Implement cloud auth. |
| // if (cloud.GetConnectionState().IsStatusEqual(ConnectionState::kOnline)) { |
| // auth_types->AppendString(kAuthTypeCloudValue); |
| // } |
| auth->Set(kAuthModeKey, auth_types.release()); |
| |
| std::unique_ptr<base::ListValue> crypto_types(new base::ListValue()); |
| for (CryptoType type : security.GetCryptoTypes()) |
| crypto_types->AppendString(EnumToString(type)); |
| auth->Set(kCryptoKey, crypto_types.release()); |
| |
| return auth; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CreateWifiSection( |
| const WifiDelegate& wifi) { |
| std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); |
| |
| std::unique_ptr<base::ListValue> capabilities(new base::ListValue()); |
| for (WifiType type : wifi.GetTypes()) |
| capabilities->AppendString(EnumToString(type)); |
| result->Set(kInfoWifiCapabilitiesKey, capabilities.release()); |
| |
| result->SetString(kInfoWifiSsidKey, wifi.GetCurrentlyConnectedSsid()); |
| |
| std::string hosted_ssid = wifi.GetHostedSsid(); |
| const ConnectionState& state = wifi.GetConnectionState(); |
| if (!hosted_ssid.empty()) { |
| DCHECK(!state.IsStatusEqual(ConnectionState::kDisabled)); |
| DCHECK(!state.IsStatusEqual(ConnectionState::kOnline)); |
| result->SetString(kInfoWifiHostedSsidKey, hosted_ssid); |
| } |
| SetStateProperties(state, result.get()); |
| return result; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> CreateGcdSection( |
| const CloudDelegate& cloud) { |
| std::unique_ptr<base::DictionaryValue> gcd(new base::DictionaryValue()); |
| gcd->SetString(kInfoIdKey, cloud.GetCloudId()); |
| SetStateProperties(cloud.GetConnectionState(), gcd.get()); |
| return gcd; |
| } |
| |
| AuthScope GetAnonymousMaxScope(const CloudDelegate& cloud, |
| const WifiDelegate* wifi) { |
| if (wifi && !wifi->GetHostedSsid().empty()) |
| return AuthScope::kNone; |
| return cloud.GetAnonymousMaxScope(); |
| } |
| |
| } // namespace |
| |
| std::vector<std::string> PrivetHandler::GetHttpPaths() const { |
| std::vector<std::string> result; |
| for (const auto& pair : handlers_) { |
| if (!pair.second.https_only) |
| result.push_back(pair.first); |
| } |
| return result; |
| } |
| |
| std::vector<std::string> PrivetHandler::GetHttpsPaths() const { |
| std::vector<std::string> result; |
| for (const auto& pair : handlers_) |
| result.push_back(pair.first); |
| return result; |
| } |
| |
| PrivetHandler::PrivetHandler(CloudDelegate* cloud, |
| DeviceDelegate* device, |
| SecurityDelegate* security, |
| WifiDelegate* wifi) |
| : cloud_(cloud), device_(device), security_(security), wifi_(wifi) { |
| CHECK(cloud_); |
| CHECK(device_); |
| CHECK(security_); |
| cloud_observer_.Add(cloud_); |
| |
| AddHandler("/privet/info", &PrivetHandler::HandleInfo, AuthScope::kNone); |
| AddHandler("/privet/v3/pairing/start", &PrivetHandler::HandlePairingStart, |
| AuthScope::kNone); |
| AddHandler("/privet/v3/pairing/confirm", &PrivetHandler::HandlePairingConfirm, |
| AuthScope::kNone); |
| AddHandler("/privet/v3/pairing/cancel", &PrivetHandler::HandlePairingCancel, |
| AuthScope::kNone); |
| |
| AddSecureHandler("/privet/v3/auth", &PrivetHandler::HandleAuth, |
| AuthScope::kNone); |
| AddSecureHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart, |
| AuthScope::kOwner); |
| AddSecureHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus, |
| AuthScope::kOwner); |
| AddSecureHandler("/privet/v3/state", &PrivetHandler::HandleState, |
| AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/commandDefs", &PrivetHandler::HandleCommandDefs, |
| AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/commands/execute", |
| &PrivetHandler::HandleCommandsExecute, AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/commands/status", |
| &PrivetHandler::HandleCommandsStatus, AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/commands/cancel", |
| &PrivetHandler::HandleCommandsCancel, AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/commands/list", |
| &PrivetHandler::HandleCommandsList, AuthScope::kViewer); |
| AddSecureHandler("/privet/v3/checkForUpdates", |
| &PrivetHandler::HandleCheckForUpdates, AuthScope::kViewer); |
| } |
| |
| PrivetHandler::~PrivetHandler() { |
| for (const auto& req : update_requests_) |
| ReplyToUpdateRequest(req.callback); |
| } |
| |
| void PrivetHandler::OnCommandDefsChanged() { |
| ++command_defs_fingerprint_; |
| auto pred = [this](const UpdateRequestParameters& params) { |
| return params.command_defs_fingerprint < 0; |
| }; |
| auto last = |
| std::partition(update_requests_.begin(), update_requests_.end(), pred); |
| for (auto p = last; p != update_requests_.end(); ++p) |
| ReplyToUpdateRequest(p->callback); |
| update_requests_.erase(last, update_requests_.end()); |
| } |
| |
| void PrivetHandler::OnStateChanged() { |
| ++state_fingerprint_; |
| auto pred = [this](const UpdateRequestParameters& params) { |
| return params.state_fingerprint < 0; |
| }; |
| auto last = |
| std::partition(update_requests_.begin(), update_requests_.end(), pred); |
| for (auto p = last; p != update_requests_.end(); ++p) |
| ReplyToUpdateRequest(p->callback); |
| update_requests_.erase(last, update_requests_.end()); |
| } |
| |
| void PrivetHandler::HandleRequest(const std::string& api, |
| const std::string& auth_header, |
| const base::DictionaryValue* input, |
| const RequestCallback& callback) { |
| ErrorPtr error; |
| if (!input) { |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kInvalidFormat, |
| "Malformed JSON"); |
| return ReturnError(*error, callback); |
| } |
| auto handler = handlers_.find(api); |
| if (handler == handlers_.end()) { |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kNotFound, |
| "Path not found"); |
| return ReturnError(*error, callback); |
| } |
| if (auth_header.empty()) { |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, |
| errors::kMissingAuthorization, |
| "Authorization header must not be empty"); |
| return ReturnError(*error, callback); |
| } |
| std::string token = GetAuthTokenFromAuthHeader(auth_header); |
| if (token.empty()) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidAuthorization, |
| "Invalid authorization header: %s", auth_header.c_str()); |
| return ReturnError(*error, callback); |
| } |
| UserInfo user_info; |
| if (token != kAuthTypeAnonymousValue) { |
| base::Time time; |
| user_info = security_->ParseAccessToken(token, &time); |
| if (user_info.scope() == AuthScope::kNone) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidAuthorization, |
| "Invalid access token: %s", token.c_str()); |
| return ReturnError(*error, callback); |
| } |
| time += base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds); |
| time += |
| base::TimeDelta::FromSeconds(kAccessTokenExpirationThresholdSeconds); |
| if (time < base::Time::Now()) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kAuthorizationExpired, "Token expired: %s", |
| token.c_str()); |
| return ReturnError(*error, callback); |
| } |
| } |
| |
| if (handler->second.scope > user_info.scope()) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidAuthorizationScope, |
| "Scope '%s' does not allow '%s'", |
| EnumToString(user_info.scope()).c_str(), api.c_str()); |
| return ReturnError(*error, callback); |
| } |
| (this->*handler->second.handler)(*input, user_info, callback); |
| } |
| |
| void PrivetHandler::AddHandler(const std::string& path, |
| ApiHandler handler, |
| AuthScope scope) { |
| HandlerParameters params; |
| params.handler = handler; |
| params.scope = scope; |
| params.https_only = false; |
| CHECK(handlers_.emplace(path, params).second); |
| } |
| |
| void PrivetHandler::AddSecureHandler(const std::string& path, |
| ApiHandler handler, |
| AuthScope scope) { |
| HandlerParameters params; |
| params.handler = handler; |
| params.scope = scope; |
| params.https_only = true; |
| CHECK(handlers_.emplace(path, params).second); |
| } |
| |
| void PrivetHandler::HandleInfo(const base::DictionaryValue&, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| base::DictionaryValue output; |
| |
| std::string name = cloud_->GetName(); |
| std::string model_id = cloud_->GetModelId(); |
| |
| output.SetString(kInfoVersionKey, kInfoVersionValue); |
| output.SetString(kInfoIdKey, cloud_->GetDeviceId()); |
| output.SetString(kNameKey, name); |
| |
| std::string description{cloud_->GetDescription()}; |
| if (!description.empty()) |
| output.SetString(kDescrptionKey, description); |
| |
| std::string location{cloud_->GetLocation()}; |
| if (!location.empty()) |
| output.SetString(kLocationKey, location); |
| |
| output.SetString(kInfoModelIdKey, model_id); |
| output.Set(kInfoModelManifestKey, CreateManifestSection(*cloud_).release()); |
| output.Set( |
| kInfoServicesKey, |
| ToValue(std::vector<std::string>{GetDeviceUiKind(cloud_->GetModelId())}) |
| .release()); |
| |
| output.Set( |
| kInfoAuthenticationKey, |
| CreateInfoAuthSection(*security_, GetAnonymousMaxScope(*cloud_, wifi_)) |
| .release()); |
| |
| output.Set(kInfoEndpointsKey, CreateEndpointsSection(*device_).release()); |
| |
| if (wifi_) |
| output.Set(kWifiKey, CreateWifiSection(*wifi_).release()); |
| |
| output.Set(kGcdKey, CreateGcdSection(*cloud_).release()); |
| |
| output.SetInteger(kInfoUptimeKey, device_->GetUptime().InSeconds()); |
| |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandlePairingStart(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| ErrorPtr error; |
| |
| std::string pairing_str; |
| input.GetString(kPairingKey, &pairing_str); |
| |
| std::string crypto_str; |
| input.GetString(kCryptoKey, &crypto_str); |
| |
| PairingType pairing; |
| std::set<PairingType> modes = security_->GetPairingTypes(); |
| if (!StringToEnum(pairing_str, &pairing) || |
| modes.find(pairing) == modes.end()) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kPairingKey, pairing_str.c_str()); |
| return ReturnError(*error, callback); |
| } |
| |
| CryptoType crypto; |
| std::set<CryptoType> cryptos = security_->GetCryptoTypes(); |
| if (!StringToEnum(crypto_str, &crypto) || |
| cryptos.find(crypto) == cryptos.end()) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kCryptoKey, crypto_str.c_str()); |
| return ReturnError(*error, callback); |
| } |
| |
| std::string id; |
| std::string commitment; |
| if (!security_->StartPairing(pairing, crypto, &id, &commitment, &error)) |
| return ReturnError(*error, callback); |
| |
| base::DictionaryValue output; |
| output.SetString(kPairingSessionIdKey, id); |
| output.SetString(kPairingDeviceCommitmentKey, commitment); |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandlePairingConfirm(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| std::string id; |
| input.GetString(kPairingSessionIdKey, &id); |
| |
| std::string commitment; |
| input.GetString(kPairingClientCommitmentKey, &commitment); |
| |
| std::string fingerprint; |
| std::string signature; |
| ErrorPtr error; |
| if (!security_->ConfirmPairing(id, commitment, &fingerprint, &signature, |
| &error)) { |
| return ReturnError(*error, callback); |
| } |
| |
| base::DictionaryValue output; |
| output.SetString(kPairingFingerprintKey, fingerprint); |
| output.SetString(kPairingSignatureKey, signature); |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandlePairingCancel(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| std::string id; |
| input.GetString(kPairingSessionIdKey, &id); |
| |
| ErrorPtr error; |
| if (!security_->CancelPairing(id, &error)) |
| return ReturnError(*error, callback); |
| |
| base::DictionaryValue output; |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandleAuth(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| ErrorPtr error; |
| |
| std::string auth_code_type; |
| input.GetString(kAuthModeKey, &auth_code_type); |
| |
| std::string auth_code; |
| input.GetString(kAuthCodeKey, &auth_code); |
| |
| AuthScope max_auth_scope = AuthScope::kNone; |
| if (auth_code_type == kAuthTypeAnonymousValue) { |
| max_auth_scope = GetAnonymousMaxScope(*cloud_, wifi_); |
| } else if (auth_code_type == kAuthTypePairingValue) { |
| if (!security_->IsValidPairingCode(auth_code)) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidAuthCode, kInvalidParamValueFormat, |
| kAuthCodeKey, auth_code.c_str()); |
| return ReturnError(*error, callback); |
| } |
| max_auth_scope = AuthScope::kOwner; |
| } else { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidAuthMode, kInvalidParamValueFormat, |
| kAuthModeKey, auth_code_type.c_str()); |
| return ReturnError(*error, callback); |
| } |
| |
| std::string requested_scope; |
| input.GetString(kAuthRequestedScopeKey, &requested_scope); |
| |
| AuthScope requested_auth_scope = |
| AuthScopeFromString(requested_scope, max_auth_scope); |
| if (requested_auth_scope == AuthScope::kNone) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidRequestedScope, kInvalidParamValueFormat, |
| kAuthRequestedScopeKey, requested_scope.c_str()); |
| return ReturnError(*error, callback); |
| } |
| |
| if (requested_auth_scope > max_auth_scope) { |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kAccessDenied, "Scope '%s' is not allowed", |
| EnumToString(requested_auth_scope).c_str()); |
| return ReturnError(*error, callback); |
| } |
| |
| base::DictionaryValue output; |
| output.SetString( |
| kAuthAccessTokenKey, |
| security_->CreateAccessToken( |
| UserInfo{requested_auth_scope, ++last_user_id_}, base::Time::Now())); |
| output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix); |
| output.SetInteger(kAuthExpiresInKey, kAccessTokenExpirationSeconds); |
| output.SetString(kAuthScopeKey, EnumToString(requested_auth_scope)); |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandleSetupStart(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| std::string name{cloud_->GetName()}; |
| input.GetString(kNameKey, &name); |
| |
| std::string description{cloud_->GetDescription()}; |
| input.GetString(kDescrptionKey, &description); |
| |
| std::string location{cloud_->GetLocation()}; |
| input.GetString(kLocationKey, &location); |
| |
| std::string ssid; |
| std::string passphrase; |
| std::string ticket; |
| std::string user; |
| |
| const base::DictionaryValue* wifi = nullptr; |
| if (input.GetDictionary(kWifiKey, &wifi)) { |
| if (!wifi_ || wifi_->GetTypes().empty()) { |
| ErrorPtr error; |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, |
| errors::kSetupUnavailable, "WiFi setup unavailable"); |
| return ReturnError(*error, callback); |
| } |
| wifi->GetString(kSetupStartSsidKey, &ssid); |
| if (ssid.empty()) { |
| ErrorPtr error; |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kSetupStartSsidKey, ""); |
| return ReturnError(*error, callback); |
| } |
| wifi->GetString(kSetupStartPassKey, &passphrase); |
| } |
| |
| const base::DictionaryValue* registration = nullptr; |
| if (input.GetDictionary(kGcdKey, ®istration)) { |
| registration->GetString(kSetupStartTicketIdKey, &ticket); |
| if (ticket.empty()) { |
| ErrorPtr error; |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kSetupStartTicketIdKey, ""); |
| return ReturnError(*error, callback); |
| } |
| registration->GetString(kSetupStartUserKey, &user); |
| } |
| |
| cloud_->UpdateDeviceInfo(name, description, location); |
| |
| ErrorPtr error; |
| if (!ssid.empty() && !wifi_->ConfigureCredentials(ssid, passphrase, &error)) |
| return ReturnError(*error, callback); |
| |
| if (!ticket.empty() && !cloud_->Setup(ticket, user, &error)) |
| return ReturnError(*error, callback); |
| |
| ReplyWithSetupStatus(callback); |
| } |
| |
| void PrivetHandler::HandleSetupStatus(const base::DictionaryValue&, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| ReplyWithSetupStatus(callback); |
| } |
| |
| void PrivetHandler::ReplyWithSetupStatus( |
| const RequestCallback& callback) const { |
| base::DictionaryValue output; |
| |
| const SetupState& state = cloud_->GetSetupState(); |
| if (!state.IsStatusEqual(SetupState::kNone)) { |
| base::DictionaryValue* gcd = new base::DictionaryValue; |
| output.Set(kGcdKey, gcd); |
| SetStateProperties(state, gcd); |
| if (state.IsStatusEqual(SetupState::kSuccess)) |
| gcd->SetString(kInfoIdKey, cloud_->GetCloudId()); |
| } |
| |
| if (wifi_) { |
| const SetupState& state = wifi_->GetSetupState(); |
| if (!state.IsStatusEqual(SetupState::kNone)) { |
| base::DictionaryValue* wifi = new base::DictionaryValue; |
| output.Set(kWifiKey, wifi); |
| SetStateProperties(state, wifi); |
| if (state.IsStatusEqual(SetupState::kSuccess)) |
| wifi->SetString(kInfoWifiSsidKey, wifi_->GetCurrentlyConnectedSsid()); |
| } |
| } |
| |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandleState(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| base::DictionaryValue output; |
| base::DictionaryValue* defs = cloud_->GetState().DeepCopy(); |
| output.Set(kStateKey, defs); |
| output.SetString(kFingerprintKey, std::to_string(state_fingerprint_)); |
| |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandleCommandDefs(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| base::DictionaryValue output; |
| base::DictionaryValue* defs = cloud_->GetCommandDef().DeepCopy(); |
| output.Set(kCommandsKey, defs); |
| output.SetString(kFingerprintKey, std::to_string(command_defs_fingerprint_)); |
| |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::HandleCommandsExecute(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| cloud_->AddCommand(input, user_info, |
| base::Bind(&OnCommandRequestSucceeded, callback)); |
| } |
| |
| void PrivetHandler::HandleCommandsStatus(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| std::string id; |
| if (!input.GetString(kCommandsIdKey, &id)) { |
| ErrorPtr error; |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kCommandsIdKey, id.c_str()); |
| return ReturnError(*error, callback); |
| } |
| cloud_->GetCommand(id, user_info, |
| base::Bind(&OnCommandRequestSucceeded, callback)); |
| } |
| |
| void PrivetHandler::HandleCommandsList(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| cloud_->ListCommands(user_info, |
| base::Bind(&OnCommandRequestSucceeded, callback)); |
| } |
| |
| void PrivetHandler::HandleCommandsCancel(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| std::string id; |
| if (!input.GetString(kCommandsIdKey, &id)) { |
| ErrorPtr error; |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, |
| errors::kInvalidParams, kInvalidParamValueFormat, |
| kCommandsIdKey, id.c_str()); |
| return ReturnError(*error, callback); |
| } |
| cloud_->CancelCommand(id, user_info, |
| base::Bind(&OnCommandRequestSucceeded, callback)); |
| } |
| |
| void PrivetHandler::HandleCheckForUpdates(const base::DictionaryValue& input, |
| const UserInfo& user_info, |
| const RequestCallback& callback) { |
| int timeout_seconds = -1; |
| input.GetInteger(kWaitTimeoutKey, &timeout_seconds); |
| base::TimeDelta timeout = device_->GetHttpRequestTimeout(); |
| // Allow 10 seconds to cut the timeout short to make sure HTTP server doesn't |
| // kill the connection before we have a chance to respond. 10 seconds chosen |
| // at random here without any scientific basis for the value. |
| const base::TimeDelta safety_gap = base::TimeDelta::FromSeconds(10); |
| if (timeout != base::TimeDelta::Max()) { |
| if (timeout > safety_gap) |
| timeout -= safety_gap; |
| else |
| timeout = base::TimeDelta::FromSeconds(0); |
| } |
| if (timeout_seconds >= 0) |
| timeout = std::min(timeout, base::TimeDelta::FromSeconds(timeout_seconds)); |
| if (timeout == base::TimeDelta{}) |
| return ReplyToUpdateRequest(callback); |
| |
| std::string state_fingerprint; |
| std::string commands_fingerprint; |
| input.GetString(kStateFingerprintKey, &state_fingerprint); |
| input.GetString(kCommandsFingerprintKey, &commands_fingerprint); |
| const bool ignore_state = state_fingerprint.empty(); |
| const bool ignore_commands = commands_fingerprint.empty(); |
| // If both fingerprints are missing, nothing to wait for, return immediately. |
| if (ignore_state && ignore_commands) |
| return ReplyToUpdateRequest(callback); |
| // If the current state fingerprint is different from the requested one, |
| // return new fingerprints. |
| if (!ignore_state && state_fingerprint != std::to_string(state_fingerprint_)) |
| return ReplyToUpdateRequest(callback); |
| // If the current commands fingerprint is different from the requested one, |
| // return new fingerprints. |
| if (!ignore_commands && |
| commands_fingerprint != std::to_string(command_defs_fingerprint_)) { |
| return ReplyToUpdateRequest(callback); |
| } |
| |
| UpdateRequestParameters params; |
| params.request_id = ++last_update_request_id_; |
| params.callback = callback; |
| params.command_defs_fingerprint = |
| ignore_commands ? -1 : command_defs_fingerprint_; |
| params.state_fingerprint = ignore_state ? -1 : state_fingerprint_; |
| update_requests_.push_back(params); |
| if (timeout != base::TimeDelta::Max()) { |
| device_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&PrivetHandler::OnUpdateRequestTimeout, |
| weak_ptr_factory_.GetWeakPtr(), last_update_request_id_), |
| timeout); |
| } |
| } |
| |
| void PrivetHandler::ReplyToUpdateRequest( |
| const RequestCallback& callback) const { |
| base::DictionaryValue output; |
| output.SetString(kStateFingerprintKey, std::to_string(state_fingerprint_)); |
| output.SetString(kCommandsFingerprintKey, |
| std::to_string(command_defs_fingerprint_)); |
| callback.Run(http::kOk, output); |
| } |
| |
| void PrivetHandler::OnUpdateRequestTimeout(int update_request_id) { |
| auto pred = [update_request_id](const UpdateRequestParameters& params) { |
| return params.request_id != update_request_id; |
| }; |
| auto last = |
| std::partition(update_requests_.begin(), update_requests_.end(), pred); |
| for (auto p = last; p != update_requests_.end(); ++p) |
| ReplyToUpdateRequest(p->callback); |
| update_requests_.erase(last, update_requests_.end()); |
| } |
| |
| } // namespace privet |
| } // namespace weave |