|  | // 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 |