blob: 8bfe753d36082f94aea8ae64d6a6d460215030d9 [file] [log] [blame]
// 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 kStateKey[] = "state";
const char kCommandsKey[] = "commands";
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, &registration)) {
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,
&registration_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,
&registration_data.client_id);
registration->GetString(kSetupStartClientSecretKey,
&registration_data.client_secret);
registration->GetString(kSetupStartApiKeyKey, &registration_data.api_key);
registration->GetString(kSetupStartOAuthUrlKey,
&registration_data.oauth_url);
registration->GetString(kSetupStartServiceUrlKey,
&registration_data.service_url);
registration->GetString(kSetupStartXmppEndpointKey,
&registration_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