forked from gojek/proctor
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from jasoet/adminAuth
[Jasoet] Enable Admin Authorization
- Loading branch information
Showing
13 changed files
with
315 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
internal/app/service/security/middleware/admin_authorization.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package middleware | ||
|
||
import ( | ||
"net/http" | ||
"proctor/internal/app/service/security/service" | ||
|
||
"github.com/gorilla/mux" | ||
|
||
"proctor/internal/app/service/infra/config" | ||
"proctor/internal/app/service/infra/logger" | ||
"proctor/pkg/auth" | ||
) | ||
|
||
type adminAuthorizationMiddleware struct { | ||
service service.SecurityService | ||
requiredGroup []string | ||
enabled bool | ||
} | ||
|
||
func (middleware *adminAuthorizationMiddleware) Secure(router *mux.Router, path string, handler http.Handler) *mux.Route { | ||
return router.NewRoute().Path(path).Handler(middleware.MiddlewareFunc(handler)) | ||
} | ||
|
||
func (middleware *adminAuthorizationMiddleware) MiddlewareFunc(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
if !middleware.enabled { | ||
next.ServeHTTP(w, r) | ||
return | ||
} | ||
|
||
userDetail := r.Context().Value(ContextUserDetailKey) | ||
if userDetail == nil { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
authorized, err := middleware.service.Verify(*(userDetail.(*auth.UserDetail)), middleware.requiredGroup) | ||
logger.LogErrors(err, "authorization", middleware.requiredGroup, userDetail) | ||
if !authorized { | ||
w.WriteHeader(http.StatusForbidden) | ||
return | ||
} | ||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
func NewAdminAuthorizationMiddleware(securityService service.SecurityService) AuthorizationMiddleware { | ||
proctorConfig := config.Config() | ||
return &adminAuthorizationMiddleware{ | ||
service: securityService, | ||
requiredGroup: proctorConfig.AuthRequiredAdminGroup, | ||
enabled: proctorConfig.AuthEnabled, | ||
} | ||
} |
215 changes: 215 additions & 0 deletions
215
internal/app/service/security/middleware/admin_authorization_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
package middleware | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/stretchr/testify/assert" | ||
|
||
"proctor/internal/app/service/security/service" | ||
"proctor/pkg/auth" | ||
) | ||
|
||
type adminAuthorizationContext struct { | ||
authorizationMiddleware adminAuthorizationMiddleware | ||
requestHandler func(http.Handler) http.Handler | ||
securityService *service.SecurityServiceMock | ||
} | ||
|
||
func (context *adminAuthorizationContext) setUp(t *testing.T) { | ||
context.authorizationMiddleware = adminAuthorizationMiddleware{} | ||
context.requestHandler = func(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
context.securityService = &service.SecurityServiceMock{} | ||
context.authorizationMiddleware.service = context.securityService | ||
context.authorizationMiddleware.enabled = true | ||
context.authorizationMiddleware.requiredGroup = []string{"proctor_admin", "system"} | ||
} | ||
|
||
func (context *adminAuthorizationContext) tearDown() { | ||
} | ||
|
||
func (context *adminAuthorizationContext) instance() *adminAuthorizationContext { | ||
return context | ||
} | ||
|
||
func newAdminAuthorizationContext() *adminAuthorizationContext { | ||
return &adminAuthorizationContext{} | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_MiddlewareFuncExecutionSuccess(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
requestBody := map[string]string{} | ||
requestBody["name"] = "a-job" | ||
body, _ := json.Marshal(requestBody) | ||
userDetail := &auth.UserDetail{ | ||
Name: "William Dembo", | ||
Email: "[email protected]", | ||
Active: true, | ||
Groups: []string{"system", "proctor_admin"}, | ||
} | ||
|
||
response := httptest.NewRecorder() | ||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) | ||
requestContext := context.WithValue(request.Context(), ContextUserDetailKey, userDetail) | ||
request = request.WithContext(requestContext) | ||
requestHandler := ctx.instance().requestHandler | ||
|
||
securityService := ctx.securityService | ||
securityService.On("Verify", *userDetail, ctx.authorizationMiddleware.requiredGroup).Return(true, nil) | ||
|
||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
|
||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
requestHandler(authzMiddleware.MiddlewareFunc(testHandler)).ServeHTTP(response, request) | ||
|
||
responseResult := response.Result() | ||
assert.Equal(t, http.StatusOK, responseResult.StatusCode) | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_MiddlewareFuncScheduleSuccess(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
requestBody := map[string]string{} | ||
requestBody["jobName"] = "a-job" | ||
body, _ := json.Marshal(requestBody) | ||
userDetail := &auth.UserDetail{ | ||
Name: "William Dembo", | ||
Email: "[email protected]", | ||
Active: true, | ||
Groups: []string{"system", "proctor_maintainer"}, | ||
} | ||
|
||
response := httptest.NewRecorder() | ||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) | ||
requestContext := context.WithValue(request.Context(), ContextUserDetailKey, userDetail) | ||
request = request.WithContext(requestContext) | ||
requestHandler := ctx.instance().requestHandler | ||
|
||
|
||
securityService := ctx.securityService | ||
securityService.On("Verify", *userDetail, ctx.authorizationMiddleware.requiredGroup).Return(true, nil) | ||
|
||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
|
||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
requestHandler(authzMiddleware.MiddlewareFunc(testHandler)).ServeHTTP(response, request) | ||
|
||
responseResult := response.Result() | ||
assert.Equal(t, http.StatusOK, responseResult.StatusCode) | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_MiddlewareFuncWithoutUserDetail(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
requestBody := map[string]string{} | ||
requestBody["name"] = "a-job" | ||
body, _ := json.Marshal(requestBody) | ||
|
||
response := httptest.NewRecorder() | ||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) | ||
requestHandler := ctx.instance().requestHandler | ||
|
||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
|
||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
requestHandler(authzMiddleware.MiddlewareFunc(testHandler)).ServeHTTP(response, request) | ||
|
||
responseResult := response.Result() | ||
assert.Equal(t, http.StatusUnauthorized, responseResult.StatusCode) | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_MiddlewareFuncFailed(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
requestBody := map[string]string{} | ||
requestBody["name"] = "a-job" | ||
body, _ := json.Marshal(requestBody) | ||
userDetail := &auth.UserDetail{ | ||
Name: "William Dembo", | ||
Email: "[email protected]", | ||
Active: true, | ||
Groups: []string{"system", "not_proctor_maintainer"}, | ||
} | ||
|
||
response := httptest.NewRecorder() | ||
request := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) | ||
requestContext := context.WithValue(request.Context(), ContextUserDetailKey, userDetail) | ||
request = request.WithContext(requestContext) | ||
requestHandler := ctx.instance().requestHandler | ||
|
||
securityService := ctx.securityService | ||
securityService.On("Verify", *userDetail, ctx.authorizationMiddleware.requiredGroup).Return(false, nil) | ||
|
||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
|
||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
requestHandler(authzMiddleware.MiddlewareFunc(testHandler)).ServeHTTP(response, request) | ||
|
||
responseResult := response.Result() | ||
assert.Equal(t, http.StatusForbidden, responseResult.StatusCode) | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_MiddlewareFuncDisabled(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
authzMiddleware.enabled = false | ||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
ts := httptest.NewServer(authzMiddleware.MiddlewareFunc(testHandler)) | ||
defer ts.Close() | ||
|
||
client := &http.Client{} | ||
req, _ := http.NewRequest("GET", ts.URL, nil) | ||
|
||
resp, _ := client.Do(req) | ||
defer resp.Body.Close() | ||
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode) | ||
} | ||
|
||
func TestAdminAuthorizationMiddleware_Secure(t *testing.T) { | ||
ctx := newAdminAuthorizationContext() | ||
ctx.setUp(t) | ||
defer ctx.tearDown() | ||
|
||
router := mux.NewRouter() | ||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
authzMiddleware := ctx.instance().authorizationMiddleware | ||
securedRouter := authzMiddleware.Secure(router, "/secure/path", handler) | ||
|
||
handledPath, err := securedRouter.GetPathTemplate() | ||
assert.NoError(t, err) | ||
assert.Equal(t, "/secure/path", handledPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.