// 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 #include #include #include #include #include #include #include #include #include #include #include #include "src/config.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 kInfoTimeKey[] = "time"; const char kInfoSessionIdKey[] = "sessionId"; 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 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 kAuthClientTokenKey[] = "clientToken"; 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 kSetupStartClientIdKey[] = "client_id"; const char kSetupStartClientSecretKey[] = "client_secret"; const char kSetupStartApiKeyKey[] = "api_key"; const char kSetupStartOAuthUrlKey[] = "oauth_url"; const char kSetupStartServiceUrlKey[] = "service_url"; const char kSetupStartXmppEndpointKey[] = "xmpp_endpoint"; std::string oauth_url; std::string client_id; std::string client_secret; std::string api_key; std::string service_url; std::string xmpp_endpoint; const char kFingerprintKey[] = "fingerprint"; const char kTraitsKey[] = "traits"; const char kComponentsKey[] = "components"; const char kCommandsIdKey[] = "id"; const char kPathKey[] = "path"; const char kFilterKey[] = "filter"; const char kStateFingerprintKey[] = "stateFingerprint"; const char kCommandsFingerprintKey[] = "commandsFingerprint"; const char kTraitsFingerprintKey[] = "traitsFingerprint"; const char kComponentsFingerprintKey[] = "componentsFingerprint"; const char kWaitTimeoutKey[] = "waitTimeout"; const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'"; template std::unique_ptr ToValue(const Container& list) { std::unique_ptr 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}, {errors::kAlreadyClaimed, http::kDenied}, }; 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 ErrorToJson(const Error& error) { std::unique_ptr output{ErrorInfoToJson(error)}; // Optional debug information. std::unique_ptr errors{new base::ListValue}; for (const Error* it = &error; it; it = it->GetInnerError()) { std::unique_ptr 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(std::move(inner)); } output->Set(kErrorDebugInfoKey, std::move(errors)); return output; } template 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())); } void ReturnError(const Error& error, const PrivetHandler::RequestCallback& callback) { int code = http::kInternalServerError; for (const auto& it : kReasonToCode) { if (error.HasError(it.reason)) { code = it.code; break; } } std::unique_ptr output{new base::DictionaryValue}; output->Set(kErrorKey, ErrorToJson(error)); 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("unknown_command")) { Error::AddTo(&error, FROM_HERE, errors::kNotFound, "Unknown command ID"); return ReturnError(*error, callback); } if (error->HasError("access_denied")) { Error::AddTo(&error, FROM_HERE, errors::kAccessDenied, error->GetMessage()); return ReturnError(*error, callback); } return ReturnError(*error, callback); } std::unique_ptr CreateManifestSection( const CloudDelegate& cloud) { std::unique_ptr 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 CreateEndpointsSection( const DeviceDelegate& device) { std::unique_ptr 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 CreateInfoAuthSection( const SecurityDelegate& security, AuthScope anonymous_max_scope) { std::unique_ptr auth(new base::DictionaryValue()); auth->SetString(kInfoAuthAnonymousMaxScopeKey, EnumToString(anonymous_max_scope)); std::unique_ptr pairing_types(new base::ListValue()); for (PairingType type : security.GetPairingTypes()) pairing_types->AppendString(EnumToString(type)); auth->Set(kPairingKey, std::move(pairing_types)); std::unique_ptr auth_types(new base::ListValue()); for (AuthType type : security.GetAuthTypes()) auth_types->AppendString(EnumToString(type)); auth->Set(kAuthModeKey, std::move(auth_types)); std::unique_ptr crypto_types(new base::ListValue()); for (CryptoType type : security.GetCryptoTypes()) crypto_types->AppendString(EnumToString(type)); auth->Set(kCryptoKey, std::move(crypto_types)); return auth; } std::unique_ptr CreateWifiSection( const WifiDelegate& wifi) { std::unique_ptr result(new base::DictionaryValue()); std::unique_ptr capabilities(new base::ListValue()); for (WifiType type : wifi.GetTypes()) capabilities->AppendString(EnumToString(type)); result->Set(kInfoWifiCapabilitiesKey, std::move(capabilities)); 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; } void SetGcdProperties(const CloudDelegate& cloud, base::DictionaryValue* dict) { dict->SetString(kInfoIdKey, cloud.GetCloudId()); dict->SetString(kSetupStartOAuthUrlKey, cloud.GetOAuthUrl()); dict->SetString(kSetupStartServiceUrlKey, cloud.GetServiceUrl()); dict->SetString(kSetupStartXmppEndpointKey, cloud.GetXmppEndpoint()); } std::unique_ptr CreateGcdSection( const CloudDelegate& cloud) { std::unique_ptr gcd(new base::DictionaryValue()); SetGcdProperties(cloud, gcd.get()); 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(); } // Forward-declaration. std::unique_ptr CloneComponentTree( const base::DictionaryValue& parent, const std::set& filter); // Clones a particular component JSON object in a manner similar to that of // DeepCopy(), except it includes only sub-objects specified in |filter| (if not // empty) and has special handling for "components" sub-dictionary. std::unique_ptr CloneComponent( const base::DictionaryValue& component, const std::set& filter) { std::unique_ptr clone{new base::DictionaryValue}; for (base::DictionaryValue::Iterator it(component); !it.IsAtEnd(); it.Advance()) { if (filter.empty() || filter.find(it.key()) != filter.end()) { if (it.key() == kComponentsKey) { // Handle "components" separately as we need to recursively clone // sub-components. const base::DictionaryValue* sub_components = nullptr; CHECK(it.value().GetAsDictionary(&sub_components)); clone->SetWithoutPathExpansion( it.key(), CloneComponentTree(*sub_components, filter)); } else { clone->SetWithoutPathExpansion(it.key(), it.value().CreateDeepCopy()); } } } return clone; } // Clones a dictionary containing a bunch of component JSON objects in a manner // similar to that of CreateDeepCopy(). Calls CloneComponent() on each instance // of the component sub-object. std::unique_ptr CloneComponentTree( const base::DictionaryValue& parent, const std::set& filter) { std::unique_ptr clone{new base::DictionaryValue}; for (base::DictionaryValue::Iterator it(parent); !it.IsAtEnd(); it.Advance()) { const base::DictionaryValue* component = nullptr; CHECK(it.value().GetAsDictionary(&component)); clone->SetWithoutPathExpansion( it.key(), CloneComponent(*component, filter)); } return clone; } } // namespace std::vector PrivetHandler::GetHttpPaths() const { std::vector result; for (const auto& pair : handlers_) { if (!pair.second.https_only) result.push_back(pair.first); } return result; } std::vector PrivetHandler::GetHttpsPaths() const { std::vector result; for (const auto& pair : handlers_) result.push_back(pair.first); return result; } PrivetHandler::PrivetHandler(CloudDelegate* cloud, DeviceDelegate* device, SecurityDelegate* security, WifiDelegate* wifi, base::Clock* clock) : cloud_(cloud), device_(device), security_(security), wifi_(wifi), clock_(clock ? clock : &default_clock_) { CHECK(cloud_); CHECK(device_); CHECK(security_); CHECK(clock_); cloud_->AddOnTraitsChangedCallback(base::Bind( &PrivetHandler::OnTraitDefsChanged, weak_ptr_factory_.GetWeakPtr())); cloud_->AddOnStateChangedCallback(base::Bind(&PrivetHandler::OnStateChanged, weak_ptr_factory_.GetWeakPtr())); cloud_->AddOnComponentsChangeCallback(base::Bind( &PrivetHandler::OnComponentTreeChanged, weak_ptr_factory_.GetWeakPtr())); 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/accessControl/claim", &PrivetHandler::HandleAccessControlClaim, AuthScope::kOwner); AddSecureHandler("/privet/v3/accessControl/confirm", &PrivetHandler::HandleAccessControlConfirm, AuthScope::kOwner); AddSecureHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart, AuthScope::kManager); AddSecureHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus, AuthScope::kManager); 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); AddSecureHandler("/privet/v3/traits", &PrivetHandler::HandleTraits, AuthScope::kViewer); AddSecureHandler("/privet/v3/components", &PrivetHandler::HandleComponents, AuthScope::kViewer); } PrivetHandler::~PrivetHandler() { for (const auto& req : update_requests_) ReplyToUpdateRequest(req.callback); } void PrivetHandler::OnTraitDefsChanged() { ++traits_fingerprint_; auto pred = [this](const UpdateRequestParameters& params) { return params.traits_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 updates also change the component tree, so update both fingerprints. ++state_fingerprint_; ++components_fingerprint_; auto pred = [this](const UpdateRequestParameters& params) { return params.state_fingerprint == 0 && params.components_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::OnComponentTreeChanged() { ++components_fingerprint_; auto pred = [this](const UpdateRequestParameters& params) { return params.components_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::kInvalidFormat, "Malformed JSON"); return ReturnError(*error, callback); } auto handler = handlers_.find(api); if (handler == handlers_.end()) { Error::AddTo(&error, FROM_HERE, errors::kNotFound, "Path not found"); return ReturnError(*error, callback); } if (auth_header.empty()) { Error::AddTo(&error, FROM_HERE, 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::kInvalidAuthorization, "Invalid authorization header: %s", auth_header.c_str()); return ReturnError(*error, callback); } UserInfo user_info; if (token != EnumToString(AuthType::kAnonymous)) { if (!security_->ParseAccessToken(token, &user_info, &error)) return ReturnError(*error, callback); } if (handler->second.scope > user_info.scope()) { Error::AddToPrintf(&error, FROM_HERE, 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_.insert(std::make_pair(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_.insert(std::make_pair(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_)); output.Set( kInfoServicesKey, ToValue(std::vector{GetDeviceUiKind(cloud_->GetModelId())})); output.Set( kInfoAuthenticationKey, CreateInfoAuthSection(*security_, GetAnonymousMaxScope(*cloud_, wifi_))); output.Set(kInfoEndpointsKey, CreateEndpointsSection(*device_)); if (wifi_) output.Set(kWifiKey, CreateWifiSection(*wifi_)); output.Set(kGcdKey, CreateGcdSection(*cloud_)); output.SetDouble(kInfoTimeKey, clock_->Now().ToJsTime()); output.SetString(kInfoSessionIdKey, security_->CreateSessionId()); 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 modes = security_->GetPairingTypes(); if (!StringToEnum(pairing_str, &pairing) || modes.find(pairing) == modes.end()) { Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, kInvalidParamValueFormat, kPairingKey, pairing_str.c_str()); return ReturnError(*error, callback); } CryptoType crypto; std::set cryptos = security_->GetCryptoTypes(); if (!StringToEnum(crypto_str, &crypto) || cryptos.find(crypto) == cryptos.end()) { Error::AddToPrintf(&error, FROM_HERE, 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; AuthType auth_type{}; if (!input.GetString(kAuthModeKey, &auth_code_type) || !StringToEnum(auth_code_type, &auth_type)) { Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidAuthMode, kInvalidParamValueFormat, kAuthModeKey, auth_code_type.c_str()); return ReturnError(*error, callback); } AuthScope desired_scope = AuthScope::kOwner; AuthScope acceptable_scope = AuthScope::kViewer; std::string requested_scope; input.GetString(kAuthRequestedScopeKey, &requested_scope); if (requested_scope != kAuthScopeAutoValue) { if (!StringToEnum(requested_scope, &desired_scope)) { Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidRequestedScope, kInvalidParamValueFormat, kAuthRequestedScopeKey, requested_scope.c_str()); return ReturnError(*error, callback); } acceptable_scope = std::max(desired_scope, acceptable_scope); } if (auth_type == AuthType::kAnonymous) desired_scope = GetAnonymousMaxScope(*cloud_, wifi_); std::string auth_code; input.GetString(kAuthCodeKey, &auth_code); std::string access_token; base::TimeDelta access_token_ttl; AuthScope access_token_scope = AuthScope::kNone; if (!security_->CreateAccessToken(auth_type, auth_code, desired_scope, &access_token, &access_token_scope, &access_token_ttl, &error)) { return ReturnError(*error, callback); } CHECK_LE(static_cast(access_token_scope), static_cast(desired_scope)); if (access_token_scope < acceptable_scope) { Error::AddToPrintf(&error, FROM_HERE, errors::kAccessDenied, "Scope '%s' is not allowed", EnumToString(access_token_scope).c_str()); return ReturnError(*error, callback); } base::DictionaryValue output; output.SetString(kAuthAccessTokenKey, access_token); output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix); output.SetInteger(kAuthExpiresInKey, access_token_ttl.InSeconds()); output.SetString(kAuthScopeKey, EnumToString(access_token_scope)); callback.Run(http::kOk, output); } void PrivetHandler::HandleAccessControlClaim(const base::DictionaryValue& input, const UserInfo& user_info, const RequestCallback& callback) { ErrorPtr error; auto token = security_->ClaimRootClientAuthToken(&error); if (token.empty()) return ReturnError(*error, callback); base::DictionaryValue output; output.SetString(kAuthClientTokenKey, token); callback.Run(http::kOk, output); } void PrivetHandler::HandleAccessControlConfirm( const base::DictionaryValue& input, const UserInfo& user_info, const RequestCallback& callback) { ErrorPtr error; std::string token; if (!input.GetString(kAuthClientTokenKey, &token)) { Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, kInvalidParamValueFormat, kAuthClientTokenKey, token.c_str()); return ReturnError(*error, callback); } if (!security_->ConfirmClientAuthToken(token, &error)) return ReturnError(*error, callback); base::DictionaryValue output; 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; RegistrationData registration_data; 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::kSetupUnavailable, "WiFi setup unavailable"); return ReturnError(*error, callback); } wifi->GetString(kSetupStartSsidKey, &ssid); if (ssid.empty()) { ErrorPtr error; Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, kInvalidParamValueFormat, kSetupStartSsidKey, ""); return ReturnError(*error, callback); } wifi->GetString(kSetupStartPassKey, &passphrase); } const base::DictionaryValue* registration = nullptr; if (input.GetDictionary(kGcdKey, ®istration)) { if (user_info.scope() < AuthScope::kOwner) { ErrorPtr error; Error::AddTo(&error, FROM_HERE, errors::kInvalidAuthorizationScope, "Only owner can register device"); return ReturnError(*error, callback); } registration->GetString(kSetupStartTicketIdKey, ®istration_data.ticket_id); if (registration_data.ticket_id.empty()) { ErrorPtr error; Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, kInvalidParamValueFormat, kSetupStartTicketIdKey, ""); return ReturnError(*error, callback); } registration->GetString(kSetupStartUserKey, &user); registration->GetString(kSetupStartClientIdKey, ®istration_data.client_id); registration->GetString(kSetupStartClientSecretKey, ®istration_data.client_secret); registration->GetString(kSetupStartApiKeyKey, ®istration_data.api_key); registration->GetString(kSetupStartOAuthUrlKey, ®istration_data.oauth_url); registration->GetString(kSetupStartServiceUrlKey, ®istration_data.service_url); registration->GetString(kSetupStartXmppEndpointKey, ®istration_data.xmpp_endpoint); } cloud_->UpdateDeviceInfo(name, description, location); ErrorPtr error; if (!ssid.empty() && !wifi_->ConfigureCredentials(ssid, passphrase, &error)) return ReturnError(*error, callback); if (!registration_data.ticket_id.empty() && !cloud_->Setup(registration_data, &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)) { SetGcdProperties(*cloud_, gcd); } } 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::HandleTraits(const base::DictionaryValue& input, const UserInfo& user_info, const RequestCallback& callback) { base::DictionaryValue output; output.Set(kTraitsKey, cloud_->GetTraits().CreateDeepCopy()); output.SetString(kFingerprintKey, std::to_string(traits_fingerprint_)); callback.Run(http::kOk, output); } void PrivetHandler::HandleComponents(const base::DictionaryValue& input, const UserInfo& user_info, const RequestCallback& callback) { std::string path; std::set filter; std::unique_ptr components; input.GetString(kPathKey, &path); const base::ListValue* filter_items = nullptr; if (input.GetList(kFilterKey, &filter_items)) { for (const base::Value* value : *filter_items) { std::string filter_item; if (value->GetAsString(&filter_item)) filter.insert(filter_item); } } const base::DictionaryValue* component = nullptr; if (!path.empty()) { ErrorPtr error; component = cloud_->FindComponent(path, &error); if (!component) return ReturnError(*error, callback); components.reset(new base::DictionaryValue); // Get the last element of the path and use it as a dictionary key here. auto parts = Split(path, ".", true, false); components->Set(parts.back(), CloneComponent(*component, filter)); } else { components = CloneComponentTree(*cloud_->GetComponentsForUser(user_info), filter); } base::DictionaryValue output; output.Set(kComponentsKey, std::move(components)); output.SetString(kFingerprintKey, std::to_string(components_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::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::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; std::string traits_fingerprint; std::string components_fingerprint; input.GetString(kStateFingerprintKey, &state_fingerprint); input.GetString(kCommandsFingerprintKey, &commands_fingerprint); input.GetString(kTraitsFingerprintKey, &traits_fingerprint); input.GetString(kComponentsFingerprintKey, &components_fingerprint); const bool ignore_state = state_fingerprint.empty(); const bool ignore_commands = commands_fingerprint.empty(); const bool ignore_traits = traits_fingerprint.empty(); const bool ignore_components = components_fingerprint.empty(); // If all fingerprints are missing, nothing to wait for, return immediately. if (ignore_state && ignore_commands && ignore_traits && ignore_components) 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. // NOTE: We are using traits fingerprint for command fingerprint as well. if (!ignore_commands && commands_fingerprint != std::to_string(traits_fingerprint_)) { return ReplyToUpdateRequest(callback); } // If the current traits fingerprint is different from the requested one, // return new fingerprints. if (!ignore_traits && traits_fingerprint != std::to_string(traits_fingerprint_)) { return ReplyToUpdateRequest(callback); } // If the current components fingerprint is different from the requested one, // return new fingerprints. if (!ignore_components && components_fingerprint != std::to_string(components_fingerprint_)) { return ReplyToUpdateRequest(callback); } UpdateRequestParameters params; params.request_id = ++last_update_request_id_; params.callback = callback; params.traits_fingerprint = (ignore_traits && ignore_commands) ? 0 : traits_fingerprint_; params.state_fingerprint = ignore_state ? 0 : state_fingerprint_; params.components_fingerprint = ignore_components ? 0 : components_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(traits_fingerprint_)); output.SetString(kTraitsFingerprintKey, std::to_string(traits_fingerprint_)); output.SetString(kComponentsFingerprintKey, std::to_string(components_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