| // 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/device.h> |
| #include <weave/enum_to_string.h> |
| #include <weave/provider/task_runner.h> |
| |
| #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 <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}, |
| {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<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(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("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<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()); |
| for (AuthType type : security.GetAuthTypes()) |
| auth_types->AppendString(EnumToString(type)); |
| 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; |
| } |
| |
| 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<base::DictionaryValue> CreateGcdSection( |
| const CloudDelegate& cloud) { |
| std::unique_ptr<base::DictionaryValue> 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<base::DictionaryValue> CloneComponentTree( |
| const base::DictionaryValue& parent, |
| const std::set<std::string>& 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<base::DictionaryValue> CloneComponent( |
| const base::DictionaryValue& component, |
| const std::set<std::string>& filter) { |
| std::unique_ptr<base::DictionaryValue> 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).release()); |
| } else { |
| clone->SetWithoutPathExpansion(it.key(), it.value().DeepCopy()); |
| } |
| } |
| } |
| return clone; |
| } |
| |
| // Clones a dictionary containing a bunch of component JSON objects in a manner |
| // similar to that of DeepCopy(). Calls CloneComponent() on each instance of |
| // the component sub-object. |
| std::unique_ptr<base::DictionaryValue> CloneComponentTree( |
| const base::DictionaryValue& parent, |
| const std::set<std::string>& filter) { |
| std::unique_ptr<base::DictionaryValue> 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).release()); |
| } |
| return clone; |
| } |
| |
| } // 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, |
| 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_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/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_).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.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<PairingType> 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<CryptoType> 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); |
| } |
| |
| 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().DeepCopy()); |
| 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<std::string> filter; |
| std::unique_ptr<base::DictionaryValue> 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).release()); |
| } else { |
| components = CloneComponentTree(cloud_->GetComponents(), filter); |
| } |
| base::DictionaryValue output; |
| output.Set(kComponentsKey, components.release()); |
| 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 |