Add session ID validation

BUG: 26292014
Change-Id: I2a71dbf3dbc4b422c8f9bedd806f459d2bc35333
Reviewed-on: https://weave-review.googlesource.com/2380
Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/src/privet/auth_manager.cc b/src/privet/auth_manager.cc
index fd15c21..0a2b75a 100644
--- a/src/privet/auth_manager.cc
+++ b/src/privet/auth_manager.cc
@@ -30,6 +30,7 @@
 const size_t kMaxMacaroonSize = 1024;
 const size_t kMaxPendingClaims = 10;
 const char kInvalidTokenError[] = "invalid_token";
+const int kSessionIdTtlMinutes = 1;
 
 uint32_t ToJ2000Time(const base::Time& time) {
   return std::max(time.ToTimeT(), kJ2000ToTimeT) - kJ2000ToTimeT;
@@ -95,11 +96,11 @@
 
 class UserIdCaveat : public Caveat {
  public:
-  explicit UserIdCaveat(const std::string& user_id)
-      : Caveat(kUwMacaroonCaveatTypeDelegateeUser, user_id.size()) {
+  explicit UserIdCaveat(const std::string& id)
+      : Caveat(kUwMacaroonCaveatTypeDelegateeUser, id.size()) {
     CHECK(uw_macaroon_caveat_create_delegatee_user_(
-        reinterpret_cast<const uint8_t*>(user_id.data()), user_id.size(),
-        buffer_.data(), buffer_.size(), &caveat_));
+        reinterpret_cast<const uint8_t*>(id.data()), id.size(), buffer_.data(),
+        buffer_.size(), &caveat_));
   }
 
   DISALLOW_COPY_AND_ASSIGN(UserIdCaveat);
@@ -117,6 +118,18 @@
 //   DISALLOW_COPY_AND_ASSIGN(ServiceCaveat);
 // };
 
+class SessionIdCaveat : public Caveat {
+ public:
+  explicit SessionIdCaveat(const std::string& id)
+      : Caveat(kUwMacaroonCaveatTypeLanSessionID, id.size()) {
+    CHECK(uw_macaroon_caveat_create_lan_session_id_(
+        reinterpret_cast<const uint8_t*>(id.data()), id.size(), buffer_.data(),
+        buffer_.size(), &caveat_));
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(SessionIdCaveat);
+};
+
 class ClientAuthTokenCaveat : public Caveat {
  public:
   ClientAuthTokenCaveat()
@@ -439,12 +452,11 @@
 
   AuthScope auth_scope{FromMacaroonScope(result.granted_scope)};
   if (auth_scope == AuthScope::kNone) {
-    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
                         "Invalid token data");
   }
 
   // TODO: Integrate black list checks.
-  // TODO: Check session id.
   auto delegates_rbegin = std::reverse_iterator<const UwMacaroonDelegateeInfo*>(
       result.delegatees + result.num_delegatees);
   auto delegates_rend =
@@ -456,10 +468,16 @@
                    });
 
   if (last_user_id == delegates_rend || !last_user_id->id_len) {
-    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthorization,
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
                         "User ID is missing");
   }
 
+  const char* session_id = reinterpret_cast<const char*>(result.lan_session_id);
+  if (!IsValidSessionId({session_id, session_id + result.lan_session_id_len})) {
+    return Error::AddTo(error, FROM_HERE, errors::kInvalidAuthCode,
+                        "Invalid session id");
+  }
+
   CHECK_GE(FromJ2000Time(result.expiration_time), now);
 
   if (!access_token)
@@ -480,27 +498,39 @@
   return true;
 }
 
-std::vector<uint8_t> AuthManager::CreateSessionId() {
-  std::vector<uint8_t> result;
-  AppendToArray(Now().ToTimeT(), &result);
-  AppendToArray(++session_counter_, &result);
-  return result;
+std::string AuthManager::CreateSessionId() const {
+  return std::to_string(ToJ2000Time(Now())) + ":" +
+         std::to_string(++session_counter_);
+}
+
+bool AuthManager::IsValidSessionId(const std::string& session_id) const {
+  base::Time ssid_time = FromJ2000Time(std::atoi(session_id.c_str()));
+  return Now() - base::TimeDelta::FromMinutes(kSessionIdTtlMinutes) <=
+             ssid_time &&
+         ssid_time <= Now();
 }
 
 std::vector<uint8_t> AuthManager::DelegateToUser(
     const std::vector<uint8_t>& token,
+    base::TimeDelta ttl,
     const UserInfo& user_info) const {
   std::vector<uint8_t> buffer;
   UwMacaroon macaroon{};
   CHECK(LoadMacaroon(token, &buffer, &macaroon, nullptr));
 
+  const base::Time now = Now();
+  TimestampCaveat issued{now};
+  ExpirationCaveat expiration{now + ttl};
   ScopeCaveat scope{ToMacaroonScope(user_info.scope())};
-  UserIdCaveat user_caveat{user_info.user_id()};
+  UserIdCaveat user{user_info.user_id()};
+  SessionIdCaveat session{CreateSessionId()};
 
-  return ExtendMacaroonToken(macaroon, Now(),
-                             {
-                                 &scope.GetCaveat(), &user_caveat.GetCaveat(),
-                             });
+  return ExtendMacaroonToken(
+      macaroon, now,
+      {
+          &issued.GetCaveat(), &expiration.GetCaveat(), &scope.GetCaveat(),
+          &user.GetCaveat(), &session.GetCaveat(),
+      });
 }
 
 }  // namespace privet
diff --git a/src/privet/auth_manager.h b/src/privet/auth_manager.h
index 0fa90a7..f0a5761 100644
--- a/src/privet/auth_manager.h
+++ b/src/privet/auth_manager.h
@@ -68,19 +68,21 @@
   void SetAuthSecret(const std::vector<uint8_t>& secret,
                      RootClientTokenOwner owner);
 
-  std::vector<uint8_t> CreateSessionId();
+  std::string CreateSessionId() const;
+  bool IsValidSessionId(const std::string& session_id) const;
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(AuthManagerClaimTest, CreateAccessTokenFromAuth);
+  friend class AuthManagerTest;
 
-  // Test helper.
+  // Test helpers. Device does not need to implement delegation.
   std::vector<uint8_t> DelegateToUser(const std::vector<uint8_t>& token,
+                                      base::TimeDelta ttl,
                                       const UserInfo& user_info) const;
 
   Config* config_{nullptr};  // Can be nullptr for tests.
   base::DefaultClock default_clock_;
   base::Clock* clock_{&default_clock_};
-  uint32_t session_counter_{0};
+  mutable uint32_t session_counter_{0};
 
   std::vector<uint8_t> auth_secret_;  // Persistent.
   std::vector<uint8_t> certificate_fingerprint_;
diff --git a/src/privet/auth_manager_unittest.cc b/src/privet/auth_manager_unittest.cc
index c5fba3c..d74abd8 100644
--- a/src/privet/auth_manager_unittest.cc
+++ b/src/privet/auth_manager_unittest.cc
@@ -29,6 +29,11 @@
   }
 
  protected:
+  std::vector<uint8_t> DelegateToUser(const std::vector<uint8_t>& token,
+                                      base::TimeDelta ttl,
+                                      const UserInfo& user_info) const {
+    return auth_.DelegateToUser(token, ttl, user_info);
+  }
   const std::vector<uint8_t> kSecret1{
       78, 40, 39, 68, 29, 19, 70, 86, 38, 61, 13, 55, 33, 32, 51, 52,
       34, 43, 97, 48, 8,  56, 11, 99, 50, 59, 24, 26, 31, 71, 76, 28};
@@ -129,6 +134,10 @@
         .WillRepeatedly(Return(kStartTime + base::TimeDelta::FromSeconds(i)));
     EXPECT_TRUE(auth.ParseAccessToken(token, &user_info, nullptr));
 
+    auto extended = DelegateToUser(token, base::TimeDelta::FromSeconds(1000),
+                                   UserInfo{AuthScope::kUser, "234"});
+    EXPECT_FALSE(auth.ParseAccessToken(extended, &user_info, nullptr));
+
     EXPECT_CALL(clock_, Now())
         .WillRepeatedly(
             Return(kStartTime + base::TimeDelta::FromSeconds(i + 1)));
@@ -176,6 +185,96 @@
   }
 }
 
+TEST_F(AuthManagerTest, CreateSessionId) {
+  EXPECT_EQ("463315200:1", auth_.CreateSessionId());
+}
+
+TEST_F(AuthManagerTest, IsValidSessionId) {
+  EXPECT_TRUE(auth_.IsValidSessionId("463315200:1"));
+  EXPECT_TRUE(auth_.IsValidSessionId("463315200:2"));
+  EXPECT_TRUE(auth_.IsValidSessionId("463315150"));
+
+  // Future
+  EXPECT_FALSE(auth_.IsValidSessionId("463315230:1"));
+
+  // Expired
+  EXPECT_FALSE(auth_.IsValidSessionId("463315100:1"));
+}
+
+TEST_F(AuthManagerTest, CreateAccessTokenFromAuth) {
+  std::vector<uint8_t> access_token;
+  AuthScope scope;
+  base::TimeDelta ttl;
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000),
+                                 UserInfo{AuthScope::kUser, "234"});
+  EXPECT_EQ(
+      "WEWIQxkgAUYIGhudoQBCCUBGCBobnaEARgUaG52k6EIBDkUJQzIzNE0RSzQ2MzMxNTIwMDox"
+      "UHN8Lm+CUQo7s84Sh+grpAE=",
+      Base64Encode(extended));
+  EXPECT_TRUE(
+      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
+                                      &access_token, &scope, &ttl, nullptr));
+  UserInfo user_info;
+  EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr));
+  EXPECT_EQ(scope, user_info.scope());
+  EXPECT_EQ(AuthScope::kUser, user_info.scope());
+
+  EXPECT_EQ("234", user_info.user_id());
+}
+
+TEST_F(AuthManagerTest, CreateAccessTokenFromAuthNotMinted) {
+  std::vector<uint8_t> access_token;
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  ErrorPtr error;
+  EXPECT_FALSE(auth_.CreateAccessTokenFromAuth(
+      root, base::TimeDelta::FromDays(1), nullptr, nullptr, nullptr, &error));
+  EXPECT_TRUE(error->HasError("invalidAuthCode"));
+}
+
+TEST_F(AuthManagerTest, CreateAccessTokenFromAuthValidateAfterSomeTime) {
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000),
+                                 UserInfo{AuthScope::kUser, "234"});
+
+  // new_time < session_id_expiration < token_expiration.
+  auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(15);
+  EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
+  EXPECT_TRUE(
+      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
+                                      nullptr, nullptr, nullptr, nullptr));
+}
+
+TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpired) {
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(10),
+                                 UserInfo{AuthScope::kUser, "234"});
+  ErrorPtr error;
+
+  // token_expiration < new_time < session_id_expiration.
+  auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(15);
+  EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
+  EXPECT_FALSE(
+      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
+                                      nullptr, nullptr, nullptr, &error));
+  EXPECT_TRUE(error->HasError("invalidAuthCode"));
+}
+
+TEST_F(AuthManagerTest, CreateAccessTokenFromAuthExpiredSessionid) {
+  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
+  auto extended = DelegateToUser(root, base::TimeDelta::FromSeconds(1000),
+                                 UserInfo{AuthScope::kUser, "234"});
+  ErrorPtr error;
+
+  // session_id_expiration < new_time < token_expiration.
+  auto new_time = clock_.Now() + base::TimeDelta::FromSeconds(200);
+  EXPECT_CALL(clock_, Now()).WillRepeatedly(Return(new_time));
+  EXPECT_FALSE(
+      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
+                                      nullptr, nullptr, nullptr, &error));
+  EXPECT_TRUE(error->HasError("invalidAuthCode"));
+}
+
 class AuthManagerClaimTest : public testing::Test {
  public:
   void SetUp() override { EXPECT_EQ(auth_.GetAuthSecret().size(), 32u); }
@@ -251,22 +350,5 @@
   EXPECT_FALSE(auth_.ConfirmClientAuthToken(token, nullptr));
 }
 
-TEST_F(AuthManagerClaimTest, CreateAccessTokenFromAuth) {
-  std::vector<uint8_t> access_token;
-  AuthScope scope;
-  base::TimeDelta ttl;
-  auto root = auth_.GetRootClientAuthToken(RootClientTokenOwner::kClient);
-  auto extended = auth_.DelegateToUser(root, UserInfo{AuthScope::kUser, "234"});
-  EXPECT_TRUE(
-      auth_.CreateAccessTokenFromAuth(extended, base::TimeDelta::FromDays(1),
-                                      &access_token, &scope, &ttl, nullptr));
-  UserInfo user_info;
-  EXPECT_TRUE(auth_.ParseAccessToken(access_token, &user_info, nullptr));
-  EXPECT_EQ(scope, user_info.scope());
-  EXPECT_EQ(AuthScope::kUser, user_info.scope());
-
-  EXPECT_EQ("234", user_info.user_id());
-}
-
 }  // namespace privet
 }  // namespace weave
diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc
index 0f00699..04164b3 100644
--- a/src/privet/security_manager.cc
+++ b/src/privet/security_manager.cc
@@ -388,7 +388,7 @@
 }
 
 std::string SecurityManager::CreateSessionId() {
-  return Base64Encode(auth_manager_->CreateSessionId());
+  return auth_manager_->CreateSessionId();
 }
 
 void SecurityManager::RegisterPairingListeners(