Skip to content

Commit 6c92dd3

Browse files
authored
[BACK-3198] Return proper 401 when session token expired in POST /v1/users/:userId/device_logs (#768)
1 parent d72b02a commit 6c92dd3

File tree

4 files changed

+256
-127
lines changed

4 files changed

+256
-127
lines changed

blob/service/api/v1/v1.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (r *Router) Routes() []*rest.Route {
4444
rest.Post("/v1/users/:userId/blobs", r.Create),
4545
rest.Delete("/v1/users/:userId/blobs", r.DeleteAll),
4646

47-
rest.Post("/v1/users/:userId/device_logs", r.CreateDeviceLogs),
47+
rest.Post("/v1/users/:userId/device_logs", api.RequireWritePermissions(r.permissionsClient, "userId", r.CreateDeviceLogs)),
4848
rest.Get("/v1/users/:userId/device_logs", api.RequireMembership(r.permissionsClient, "userId", r.ListDeviceLogs)),
4949

5050
rest.Get("/v1/blobs/:id", r.Get),
@@ -184,11 +184,6 @@ func (r *Router) CreateDeviceLogs(res rest.ResponseWriter, req *rest.Request) {
184184
content.StartAt = startAtTime
185185
content.EndAt = endAtTime
186186

187-
_, err = r.AuthClient().EnsureAuthorizedUser(req.Context(), userID, permission.Write)
188-
if responder.RespondIfError(err) {
189-
return
190-
}
191-
192187
result, err := r.Provider.BlobClient().CreateDeviceLogs(req.Context(), userID, content)
193188
if err != nil {
194189
if errors.Code(err) == request.ErrorCodeDigestsNotEqual {

blob/service/api/v1/v1_test.go

Lines changed: 173 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -936,177 +936,232 @@ var _ = Describe("V1", func() {
936936
})
937937

938938
Context("responds with JSON", func() {
939-
BeforeEach(func() {
940-
res.WriteOutputs = []testRest.WriteOutput{{BytesWritten: 0, Error: nil}}
941-
})
942-
943-
AfterEach(func() {
944-
Expect(res.HeaderOutput).To(Equal(&http.Header{"Content-Type": []string{"application/json; charset=utf-8"}}))
945-
})
946-
947-
When("the path does not contain a user id", func() {
948-
BeforeEach(func() {
949-
req.URL.Path = "/v1/users//device_logs"
950-
})
951-
952-
It("responds with bad request and expected error in body", func() {
953-
handlerFunc(res, req)
954-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
955-
Expect(res.WriteInputs).To(HaveLen(1))
956-
errorsTest.ExpectErrorJSON(request.ErrorParameterMissing("userId"), res.WriteInputs[0])
957-
})
958-
})
959-
960-
When("the path contains an invalid user id", func() {
961-
BeforeEach(func() {
962-
req.URL.Path = "/v1/users/invalid/device_logs"
963-
})
964-
965-
It("responds with bad request and expected error in body", func() {
966-
handlerFunc(res, req)
967-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
968-
Expect(res.WriteInputs).To(HaveLen(1))
969-
errorsTest.ExpectErrorJSON(request.ErrorParameterInvalid("userId"), res.WriteInputs[0])
970-
})
971-
})
972-
973-
Context("with content", func() {
974-
var content *blob.DeviceLogsContent
975939

940+
Context("with clients", func() {
941+
var details request.AuthDetails
942+
var authClient *authTest.Client
943+
var client *blobTest.Client
944+
var originalUnauthHTTPReq *http.Request
945+
var originalUnauthContext context.Context
976946
BeforeEach(func() {
977-
content = blobTest.RandomDeviceLogsContent()
947+
authClient = authTest.NewClient()
948+
client = blobTest.NewClient()
949+
res.WriteOutputs = []testRest.WriteOutput{{BytesWritten: 0, Error: nil}}
950+
provider.BlobClientOutputs = []blob.Client{client}
951+
provider.AuthClientOutputs = nil
952+
// Because this using the newer authentication
953+
// paradigm, see note in package
954+
// api.RequireAuth for reason why
955+
// provider.AuthClientOutputs is not set. the
956+
// presence of AuthDetails in the context notes
957+
// the user / server has already authenticated.
958+
// The AuthClientOutputs would be non empty if
959+
// an ADDITIONAL call to use the auth client -
960+
// for example to get permissions from user A
961+
// to user B were required
962+
963+
originalUnauthHTTPReq = req.Request
964+
originalUnauthContext = req.Context()
965+
details = request.NewAuthDetails(request.MethodSessionToken, userID, authTest.NewSessionToken())
966+
req.Request = req.WithContext(request.NewContextWithAuthDetails(originalUnauthContext, details))
967+
authClient.GetUserPermissionsStub = func(ctx context.Context, requestUserID string, targetUserID string) (permission.Permissions, error) {
968+
authDetails := request.GetAuthDetails(ctx)
969+
if authDetails == nil {
970+
return nil, request.ErrorUnauthenticated()
971+
}
972+
if authDetails.IsService() || requestUserID == targetUserID {
973+
return permission.Permissions{
974+
permission.Owner: permission.Permission{},
975+
}, nil
976+
}
977+
return nil, request.ErrorUnauthorized()
978+
}
978979
})
979980

980-
JustBeforeEach(func() {
981-
req.Body = io.NopCloser(content.Body)
982-
if content.DigestMD5 != nil {
983-
req.Header.Set("Digest", fmt.Sprintf("md5=%s", *content.DigestMD5))
984-
}
985-
if content.MediaType != nil {
986-
req.Header.Set("Content-Type", *content.MediaType)
987-
}
988-
if content.EndAt != nil {
989-
req.Header.Set("X-Logs-End-At-Time", content.EndAt.Format(time.RFC3339Nano))
990-
}
991-
if content.StartAt != nil {
992-
req.Header.Set("X-Logs-Start-At-Time", content.StartAt.Format(time.RFC3339Nano))
993-
}
981+
AfterEach(func() {
982+
Expect(res.HeaderOutput).To(Equal(&http.Header{"Content-Type": []string{"application/json; charset=utf-8"}}))
994983
})
995984

996-
When("the digest header is invalid", func() {
985+
When("the path does not contain a user id", func() {
997986
BeforeEach(func() {
998-
content.DigestMD5 = pointer.FromString("invalid")
987+
req.URL.Path = "/v1/users//device_logs"
999988
})
1000989

1001990
It("responds with bad request and expected error in body", func() {
991+
provider.BlobClientOutputs = nil
1002992
handlerFunc(res, req)
1003993
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1004994
Expect(res.WriteInputs).To(HaveLen(1))
1005-
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Digest"), res.WriteInputs[0])
995+
errorsTest.ExpectErrorJSON(request.ErrorParameterMissing("userId"), res.WriteInputs[0])
1006996
})
1007997
})
1008998

1009-
When("the digest header is invalid with multiple values", func() {
1010-
JustBeforeEach(func() {
1011-
req.Header.Add("Digest", fmt.Sprintf("md5=%s", *content.DigestMD5))
1012-
})
1013-
1014-
It("responds with bad request and expected error in body", func() {
1015-
handlerFunc(res, req)
1016-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1017-
Expect(res.WriteInputs).To(HaveLen(1))
1018-
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Digest"), res.WriteInputs[0])
1019-
})
1020-
})
1021-
When("the digest header is missing", func() {
999+
When("the path contains an invalid user id", func() {
10221000
BeforeEach(func() {
1023-
content.DigestMD5 = nil
1001+
req.URL.Path = "/v1/users/invalid/device_logs"
10241002
})
10251003

10261004
It("responds with bad request and expected error in body", func() {
1005+
provider.BlobClientOutputs = nil
10271006
handlerFunc(res, req)
10281007
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
10291008
Expect(res.WriteInputs).To(HaveLen(1))
1030-
errorsTest.ExpectErrorJSON(request.ErrorHeaderMissing("Digest"), res.WriteInputs[0])
1009+
errorsTest.ExpectErrorJSON(request.ErrorParameterInvalid("userId"), res.WriteInputs[0])
10311010
})
10321011
})
10331012

1034-
When("the content type header is missing", func() {
1013+
Context("with content", func() {
1014+
var content *blob.DeviceLogsContent
1015+
10351016
BeforeEach(func() {
1036-
content.MediaType = nil
1017+
content = blobTest.RandomDeviceLogsContent()
10371018
})
10381019

1039-
It("responds with bad request and expected error in body", func() {
1040-
handlerFunc(res, req)
1041-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1042-
Expect(res.WriteInputs).To(HaveLen(1))
1043-
errorsTest.ExpectErrorJSON(request.ErrorHeaderMissing("Content-Type"), res.WriteInputs[0])
1020+
JustBeforeEach(func() {
1021+
req.Body = io.NopCloser(content.Body)
1022+
if content.DigestMD5 != nil {
1023+
req.Header.Set("Digest", fmt.Sprintf("md5=%s", *content.DigestMD5))
1024+
}
1025+
if content.MediaType != nil {
1026+
req.Header.Set("Content-Type", *content.MediaType)
1027+
}
1028+
if content.EndAt != nil {
1029+
req.Header.Set("X-Logs-End-At-Time", content.EndAt.Format(time.RFC3339Nano))
1030+
}
1031+
if content.StartAt != nil {
1032+
req.Header.Set("X-Logs-Start-At-Time", content.StartAt.Format(time.RFC3339Nano))
1033+
}
10441034
})
1045-
})
10461035

1047-
When("the content type header is invalid", func() {
1048-
BeforeEach(func() {
1049-
content.MediaType = pointer.FromString("invalid type")
1036+
When("the digest header is invalid", func() {
1037+
BeforeEach(func() {
1038+
content.DigestMD5 = pointer.FromString("invalid")
1039+
})
1040+
1041+
It("responds with bad request and expected error in body", func() {
1042+
provider.BlobClientOutputs = nil
1043+
handlerFunc(res, req)
1044+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1045+
Expect(res.WriteInputs).To(HaveLen(1))
1046+
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Digest"), res.WriteInputs[0])
1047+
})
10501048
})
10511049

1052-
It("responds with bad request and expected error in body", func() {
1053-
handlerFunc(res, req)
1054-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1055-
Expect(res.WriteInputs).To(HaveLen(1))
1056-
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Content-Type"), res.WriteInputs[0])
1050+
When("the digest header is invalid with multiple values", func() {
1051+
JustBeforeEach(func() {
1052+
req.Header.Add("Digest", fmt.Sprintf("md5=%s", *content.DigestMD5))
1053+
})
1054+
1055+
It("responds with bad request and expected error in body", func() {
1056+
provider.BlobClientOutputs = nil
1057+
handlerFunc(res, req)
1058+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1059+
Expect(res.WriteInputs).To(HaveLen(1))
1060+
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Digest"), res.WriteInputs[0])
1061+
})
10571062
})
1058-
})
1063+
When("the digest header is missing", func() {
1064+
BeforeEach(func() {
1065+
content.DigestMD5 = nil
1066+
})
10591067

1060-
When("the content type header is invalid with multiple values", func() {
1061-
JustBeforeEach(func() {
1062-
req.Header.Add("Content-Type", *content.MediaType)
1068+
It("responds with bad request and expected error in body", func() {
1069+
provider.BlobClientOutputs = nil
1070+
handlerFunc(res, req)
1071+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1072+
Expect(res.WriteInputs).To(HaveLen(1))
1073+
errorsTest.ExpectErrorJSON(request.ErrorHeaderMissing("Digest"), res.WriteInputs[0])
1074+
})
10631075
})
10641076

1065-
It("responds with bad request and expected error in body", func() {
1066-
handlerFunc(res, req)
1067-
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1068-
Expect(res.WriteInputs).To(HaveLen(1))
1069-
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Content-Type"), res.WriteInputs[0])
1077+
When("the content type header is missing", func() {
1078+
BeforeEach(func() {
1079+
content.MediaType = nil
1080+
})
1081+
1082+
It("responds with bad request and expected error in body", func() {
1083+
provider.BlobClientOutputs = nil
1084+
handlerFunc(res, req)
1085+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1086+
Expect(res.WriteInputs).To(HaveLen(1))
1087+
errorsTest.ExpectErrorJSON(request.ErrorHeaderMissing("Content-Type"), res.WriteInputs[0])
1088+
})
10701089
})
1071-
})
10721090

1073-
Context("with clients", func() {
1074-
var authClient *authTest.Client
1075-
var client *blobTest.Client
1091+
When("the content type header is invalid", func() {
1092+
BeforeEach(func() {
1093+
content.MediaType = pointer.FromString("invalid type")
1094+
})
10761095

1077-
BeforeEach(func() {
1078-
authClient = authTest.NewClient()
1079-
client = blobTest.NewClient()
1080-
provider.BlobClientOutputs = []blob.Client{client}
1081-
provider.AuthClientOutputs = []auth.Client{authClient}
1096+
It("responds with bad request and expected error in body", func() {
1097+
provider.BlobClientOutputs = nil
1098+
handlerFunc(res, req)
1099+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1100+
Expect(res.WriteInputs).To(HaveLen(1))
1101+
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Content-Type"), res.WriteInputs[0])
1102+
})
10821103
})
10831104

1084-
BeforeEach(func() {
1085-
authClient.EnsureAuthorizedUserOutputs = []authTest.EnsureAuthorizedUserOutput{{
1086-
AuthorizedUserID: userID,
1087-
Error: nil,
1088-
}}
1105+
When("the content type header is invalid with multiple values", func() {
1106+
JustBeforeEach(func() {
1107+
req.Header.Add("Content-Type", *content.MediaType)
1108+
})
1109+
1110+
It("responds with bad request and expected error in body", func() {
1111+
provider.BlobClientOutputs = nil
1112+
handlerFunc(res, req)
1113+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
1114+
Expect(res.WriteInputs).To(HaveLen(1))
1115+
errorsTest.ExpectErrorJSON(request.ErrorHeaderInvalid("Content-Type"), res.WriteInputs[0])
1116+
})
10891117
})
10901118

10911119
AfterEach(func() {
1092-
Expect(authClient.EnsureAuthorizedUserInputs[0].TargetUserID).To(Equal(userID))
1093-
Expect(authClient.EnsureAuthorizedUserInputs[0].AuthorizedPermission).To(Equal(permission.Write))
10941120
authClient.AssertOutputsEmpty()
10951121
client.AssertOutputsEmpty()
10961122
})
10971123

1098-
When("the client returns an unauthorized error", func() {
1099-
It("responds with an unauthorized error", func() {
1124+
When("unauthenticated", func() {
1125+
It("responds with an unauthenticated error", func() {
1126+
provider.BlobClientOutputs = nil
1127+
provider.AuthClientOutputs = nil
1128+
req.Request = originalUnauthHTTPReq
1129+
1130+
handlerFunc(res, req)
1131+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusUnauthorized}))
1132+
Expect(res.WriteInputs).To(HaveLen(1))
1133+
errorsTest.ExpectErrorJSON(request.ErrorUnauthenticated(), res.WriteInputs[0])
1134+
})
1135+
})
1136+
When("the client uploads for another user", func() {
1137+
var otherUserID string
1138+
BeforeEach(func() {
1139+
provider.AuthClientOutputs = []auth.Client{authClient}
1140+
otherUserID = userTest.RandomID()
1141+
})
1142+
1143+
It("returns an unauthorized error for a user uploading for another user", func() {
1144+
req.URL.Path = fmt.Sprintf("/v1/users/%s/device_logs", otherUserID)
11001145
provider.BlobClientOutputs = nil
1101-
authClient.EnsureAuthorizedUserOutputs = []authTest.EnsureAuthorizedUserOutput{{
1102-
AuthorizedUserID: "",
1103-
Error: request.ErrorUnauthorized(),
1104-
}}
11051146
handlerFunc(res, req)
11061147
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusForbidden}))
11071148
Expect(res.WriteInputs).To(HaveLen(1))
11081149
errorsTest.ExpectErrorJSON(request.ErrorUnauthorized(), res.WriteInputs[0])
11091150
})
1151+
1152+
It("services responds successfully", func() {
1153+
provider.AuthClientOutputs = nil // AuthClient not invoked if already determined to be a service
1154+
details = request.NewAuthDetails(request.MethodServiceSecret, "", authTest.NewSessionToken())
1155+
req.Request = req.WithContext(request.NewContextWithAuthDetails(originalUnauthContext, details))
1156+
req.URL.Path = fmt.Sprintf("/v1/users/%s/device_logs", otherUserID)
1157+
1158+
responseResult := blobTest.RandomDeviceLogsBlob()
1159+
client.CreateDeviceLogsOutputs = []blobTest.CreateDeviceLogsOutput{{Blob: responseResult, Error: nil}}
1160+
handlerFunc(res, req)
1161+
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusCreated}))
1162+
Expect(res.WriteInputs).To(HaveLen(1))
1163+
Expect(json.Marshal(responseResult)).To(MatchJSON(res.WriteInputs[0]))
1164+
})
11101165
})
11111166

11121167
When("the digest header is specified", func() {
@@ -1125,6 +1180,7 @@ var _ = Describe("V1", func() {
11251180
It("responds with a bad request error when the client returns a digests not equal error", func() {
11261181
err := request.ErrorDigestsNotEqual(cryptoTest.RandomBase64EncodedMD5Hash(), cryptoTest.RandomBase64EncodedMD5Hash())
11271182
client.CreateDeviceLogsOutputs = []blobTest.CreateDeviceLogsOutput{{Blob: nil, Error: err}}
1183+
provider.BlobClientOutputs = []blob.Client{client}
11281184
handlerFunc(res, req)
11291185
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusBadRequest}))
11301186
Expect(res.WriteInputs).To(HaveLen(1))
@@ -1133,6 +1189,7 @@ var _ = Describe("V1", func() {
11331189

11341190
It("responds with an internal server error when the client returns an unknown error", func() {
11351191
client.CreateDeviceLogsOutputs = []blobTest.CreateDeviceLogsOutput{{Blob: nil, Error: errorsTest.RandomError()}}
1192+
provider.BlobClientOutputs = []blob.Client{client}
11361193
handlerFunc(res, req)
11371194
Expect(res.WriteHeaderInputs).To(Equal([]int{http.StatusInternalServerError}))
11381195
Expect(res.WriteInputs).To(HaveLen(1))

0 commit comments

Comments
 (0)