From e28e95963b018aad052b46e1a4e8a0124a08c4e8 Mon Sep 17 00:00:00 2001 From: Anant Prakash Date: Tue, 22 May 2018 14:50:51 +0530 Subject: [PATCH] Add macaroon token handlers --- .../clientapi/auth/tokens/tokens_handlers.go | 115 ++++++++++++++++++ .../auth/tokens/tokens_handlers_test.go | 56 +++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers.go create mode 100644 src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers_test.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers.go b/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers.go new file mode 100644 index 000000000..14d1f0e20 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers.go @@ -0,0 +1,115 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tokens + +import ( + "errors" + "net/http" + "strconv" + "strings" + "time" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/util" +) + +// GetUserFromToken returns the user associated with the token +// Returns the error if something goes wrong. +// Warning: Does not validate the token. Use ValidateToken for that. +func GetUserFromToken(token string) (user string, err error) { + mac, err := deSerializeMacaroon(token) + if err != nil { + return + } + + user = string(mac.Id()[:]) + return +} + +// ValidateToken validates that the Token is understood and was signed by this server. +// Returns nil if token is valid, otherwise returns a error response which can be sent to client. +func ValidateToken(op TokenOptions, token string) *util.JSONResponse { + macaroon, err := deSerializeMacaroon(token) + if err != nil { + return &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.UnknownToken("Token does not represent a valid macaroon"), + } + } + + caveats, err := macaroon.VerifySignature(op.ServerPrivateKey, nil) + if err != nil { + return &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.UnknownToken("Provided token was not issued by this server"), + } + } + + err = verifyCaveats(caveats, op.UserID) + if err != nil { + return &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Provided token not authorized"), + } + } + return nil +} + +// verifyCaveats verifies caveats associated with a login token macaroon. +// which are "gen = 1", "user_id = ...", "time < ..." +// Returns nil on successful verification, else returns an error. +func verifyCaveats(caveats []string, userID string) error { + // variable verified represents a bitmap + // last 4 bit is Uvvv where, + // U: unknownCaveat + // v: caveat to be verified + var verified uint8 + now := time.Now().Second() + +LoopCaveat: + for _, caveat := range caveats { + switch { + case caveat == Gen: + verified |= 1 + case strings.HasPrefix(caveat, UserPrefix): + if caveat[len(UserPrefix):] == userID { + verified |= 2 + } + case strings.HasPrefix(caveat, TimePrefix): + if verifyExpiry(caveat[len(TimePrefix):], now) { + verified |= 4 + } + default: + verified |= 8 + break LoopCaveat + } + } + // Check that all three caveats are verified and no extra caveats + // i.e. Uvvv == 0111 + if verified == 7 { + return nil + } else if verified >= 8 { + return errors.New("Unknown caveat present") + } + + return errors.New("Required caveats not present") +} + +func verifyExpiry(t string, now int) bool { + expiry, err := strconv.Atoi(t) + + if err != nil { + return false + } + return now < expiry +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers_test.go b/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers_test.go new file mode 100644 index 000000000..2a71598c7 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/tokens/tokens_handlers_test.go @@ -0,0 +1,56 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tokens + +import "testing" + +// If any of these options are missing, validation should fail +var invalidMissings = []string{"ServerPrivateKey", "UserID"} + +func TestValidateToken(t *testing.T) { + fakeToken, err := GenerateLoginToken(validTokenOp) + if err != nil { + t.Errorf("Token generation failed for valid TokenOptions with err: %s", err.Error()) + } + + // Test validation + res := ValidateToken(validTokenOp, fakeToken) + if res != nil { + t.Error("Token validation failed with response: ", *res) + } + + // Test validation fails for invalid TokenOp + for _, invalidMissing := range invalidMissings { + res = ValidateToken(invalidTokenOps[invalidMissing], fakeToken) + if res == nil { + t.Errorf("Token validation should fail for TokenOptions with missing %s", invalidMissing) + } + } +} + +func TestGetUserFromToken(t *testing.T) { + fakeToken, err := GenerateLoginToken(validTokenOp) + if err != nil { + t.Errorf("Token generation failed for valid TokenOptions with err: %s", err.Error()) + } + + // Test validation + name, err := GetUserFromToken(fakeToken) + if err != nil { + t.Error("Failed to get userID from Token: ", err) + } + + if name != validTokenOp.UserID { + t.Error("UserID from Token doesn't match, got: ", name, " expected: ", validTokenOp.UserID) + } +}