Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/UI overhaul #1766

Merged
merged 13 commits into from
Apr 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions api/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ servers:
security:
- jwt_token: []
- basic_auth: []
- cookie_auth: []

components:
securitySchemes:
Expand All @@ -22,8 +23,18 @@ components:
type: apiKey
in: header
name: X-JWT-Authorization
cookie_auth:
type: apiKey
in: cookie
name: access_token

parameters:
PaginationPrefix:
in: query
name: prefix
description: return items prefixed with this value
schema:
type: string
PaginationAfter:
in: query
name: after
Expand Down Expand Up @@ -569,6 +580,26 @@ components:
items:
$ref: "#/components/schemas/User"

LoginInformation:
type: object
required:
- access_key_id
- secret_access_key
properties:
access_key_id:
type: string
secret_access_key:
type: string

AuthenticationToken:
type: object
required:
- token
properties:
token:
description: a JWT token that could be used to authenticate requests
type: string

GroupCreation:
type: object
required:
Expand Down Expand Up @@ -800,13 +831,59 @@ paths:
schema:
$ref: "#/components/schemas/CurrentUser"

/auth/login:
post:
tags:
- auth
operationId: login
summary: perform a login
security: [] # No authentication
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/LoginInformation"
responses:
200:
description: successful login
headers:
Set-Cookie:
schema:
type: string
example: "access_token=abcde12356; Path=/; HttpOnly"
content:
application/json:
schema:
$ref: "#/components/schemas/AuthenticationToken"
401:
$ref: "#/components/responses/Unauthorized"
default:
$ref: "#/components/responses/ServerError"

/auth/logout:
post:
tags:
- auth
operationId: logout
summary: logs out a cookie-authenticated user
security:
- cookie_auth: []
responses:
200:
description: successful logout
401:
$ref: "#/components/responses/Unauthorized"
default:
$ref: "#/components/responses/ServerError"

/auth/users:
get:
tags:
- auth
operationId: listUsers
summary: list users
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
responses:
Expand Down Expand Up @@ -895,6 +972,7 @@ paths:
operationId: listGroups
summary: list groups
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
responses:
Expand Down Expand Up @@ -979,6 +1057,7 @@ paths:
operationId: listPolicies
summary: list policies
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
responses:
Expand Down Expand Up @@ -1094,6 +1173,7 @@ paths:
operationId: listGroupMembers
summary: list group members
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
responses:
Expand Down Expand Up @@ -1161,6 +1241,7 @@ paths:
tags:
- auth
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
operationId: listUserCredentials
Expand Down Expand Up @@ -1255,6 +1336,7 @@ paths:
tags:
- auth
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
operationId: listUserGroups
Expand Down Expand Up @@ -1284,6 +1366,7 @@ paths:
tags:
- auth
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
- in: query
Expand Down Expand Up @@ -1360,6 +1443,7 @@ paths:
tags:
- auth
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
operationId: listGroupPolicies
Expand Down Expand Up @@ -1425,6 +1509,7 @@ paths:
tags:
- repositories
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
operationId: listRepositories
Expand Down Expand Up @@ -1684,6 +1769,7 @@ paths:
operationId: listBranches
summary: list branches
parameters:
- $ref: "#/components/parameters/PaginationPrefix"
- $ref: "#/components/parameters/PaginationAfter"
- $ref: "#/components/parameters/PaginationAmount"
responses:
Expand Down
2 changes: 1 addition & 1 deletion cmd/lakefs/cmd/diagnose.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var diagnoseCmd = &cobra.Command{
defer func() { _ = c.Close() }()

numFailures := 0
repos, _, err := c.ListRepositories(ctx, -1, "")
repos, _, err := c.ListRepositories(ctx, -1, "", "")
if err != nil {
// Cannot advance last so fail everything
logger.WithField("error", err).Fatal("Failed to list repositories")
Expand Down
3 changes: 2 additions & 1 deletion pkg/api/auth_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"context"
"crypto/subtle"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -106,7 +107,7 @@ func userByAuth(ctx context.Context, logger logging.Logger, authService auth.Ser
logger.WithError(err).Error("failed getting credentials for key")
return nil, ErrAuthenticationFailed
}
if secretKey != cred.SecretAccessKey {
if subtle.ConstantTimeCompare([]byte(secretKey), []byte(cred.SecretAccessKey)) != 1 {
logger.Debug("access key secret does not match")
return nil, ErrAuthenticationFailed
}
Expand Down
66 changes: 62 additions & 4 deletions pkg/api/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
type contextKey string

const (
// Maximum amount of results returned for paginated queries to the API
// DefaultMaxPerPage is the maximum amount of results returned for paginated queries to the API
DefaultMaxPerPage int = 1000
lakeFSPrefix = "symlinks"
UserContextKey contextKey = "user"
Expand Down Expand Up @@ -62,6 +62,49 @@ type Controller struct {
Logger logging.Logger
}

func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: JWTCookieName,
Value: "",
Path: "/",
HttpOnly: true,
Expires: time.Unix(0, 0),
SameSite: http.SameSiteStrictMode,
})

writeResponse(w, http.StatusOK, nil)
}

func (c *Controller) Login(w http.ResponseWriter, r *http.Request, body LoginJSONRequestBody) {
ctx := r.Context()
_, err := userByAuth(ctx, c.Logger, c.Auth, body.AccessKeyId, body.SecretAccessKey)
if errors.Is(err, ErrAuthenticationFailed) {
writeResponse(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}

loginTime := time.Now()
expires := loginTime.Add(DefaultLoginExpiration)
secret := c.Auth.SecretStore().SharedSecret()
tokenString, err := GenerateJWT(secret, body.AccessKeyId, loginTime, expires)
if err != nil {
writeError(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return
}

http.SetCookie(w, &http.Cookie{
Name: JWTCookieName,
Value: tokenString,
Path: "/",
Expires: expires,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
response := AuthenticationToken{
Token: tokenString,
}
writeResponse(w, http.StatusOK, response)
}

func (c *Controller) GetPhysicalAddress(w http.ResponseWriter, r *http.Request, repository string, branch string, params GetPhysicalAddressParams) {
if !c.authorize(w, r, []permissions.Permission{
{
Expand Down Expand Up @@ -202,6 +245,7 @@ func (c *Controller) ListGroups(w http.ResponseWriter, r *http.Request, params L
c.LogAction(ctx, "list_groups")
groups, paginator, err := c.Auth.ListGroups(ctx, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -316,6 +360,7 @@ func (c *Controller) ListGroupMembers(w http.ResponseWriter, r *http.Request, gr
c.LogAction(ctx, "list_group_users")
users, paginator, err := c.Auth.ListGroupUsers(ctx, groupID, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -390,6 +435,7 @@ func (c *Controller) ListGroupPolicies(w http.ResponseWriter, r *http.Request, g
c.LogAction(ctx, "list_user_policies")
policies, paginator, err := c.Auth.ListGroupPolicies(ctx, groupID, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -479,6 +525,7 @@ func (c *Controller) ListPolicies(w http.ResponseWriter, r *http.Request, params
c.LogAction(ctx, "list_policies")
policies, paginator, err := c.Auth.ListPolicies(ctx, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -627,6 +674,7 @@ func (c *Controller) ListUsers(w http.ResponseWriter, r *http.Request, params Li
c.LogAction(ctx, "list_users")
users, paginator, err := c.Auth.ListUsers(ctx, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -738,6 +786,7 @@ func (c *Controller) ListUserCredentials(w http.ResponseWriter, r *http.Request,
c.LogAction(ctx, "list_user_credentials")
credentials, paginator, err := c.Auth.ListUserCredentials(ctx, userID, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -847,6 +896,7 @@ func (c *Controller) ListUserGroups(w http.ResponseWriter, r *http.Request, user
c.LogAction(ctx, "list_user_groups")
groups, paginator, err := c.Auth.ListUserGroups(ctx, userID, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -891,6 +941,7 @@ func (c *Controller) ListUserPolicies(w http.ResponseWriter, r *http.Request, us
}
policies, paginator, err := listPolicies(ctx, userID, &model.PaginationParams{
After: paginationAfter(params.After),
Prefix: paginationPrefix(params.Prefix),
Amount: paginationAmount(params.Amount),
})
if handleAPIError(w, err) {
Expand Down Expand Up @@ -981,7 +1032,7 @@ func (c *Controller) ListRepositories(w http.ResponseWriter, r *http.Request, pa
ctx := r.Context()
c.LogAction(ctx, "list_repos")

repos, hasMore, err := c.Catalog.ListRepositories(ctx, paginationAmount(params.Amount), paginationAfter(params.After))
repos, hasMore, err := c.Catalog.ListRepositories(ctx, paginationAmount(params.Amount), paginationPrefix(params.Prefix), paginationAfter(params.After))
if err != nil {
writeError(w, http.StatusInternalServerError, fmt.Sprintf("error listing repositories: %s", err))
return
Expand Down Expand Up @@ -1369,7 +1420,7 @@ func (c *Controller) ListBranches(w http.ResponseWriter, r *http.Request, reposi
ctx := r.Context()
c.LogAction(ctx, "list_branches")

res, hasMore, err := c.Catalog.ListBranches(ctx, repository, "", paginationAmount(params.Amount), paginationAfter(params.After))
res, hasMore, err := c.Catalog.ListBranches(ctx, repository, paginationPrefix(params.Prefix), paginationAmount(params.Amount), paginationAfter(params.After))
if handleAPIError(w, err) {
return
}
Expand Down Expand Up @@ -2651,6 +2702,13 @@ func paginationAfter(v *PaginationAfter) string {
return string(*v)
}

func paginationPrefix(v *PaginationPrefix) string {
if v == nil {
return ""
}
return string(*v)
}

func paginationAmount(v *PaginationAmount) int {
if v == nil {
return DefaultMaxPerPage
Expand Down Expand Up @@ -2723,7 +2781,7 @@ func (c *Controller) authorize(w http.ResponseWriter, r *http.Request, perms []p
ctx := r.Context()
user, ok := ctx.Value(UserContextKey).(*model.User)
if !ok || user == nil {
writeError(w, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
writeError(w, http.StatusUnauthorized, ErrAuthenticationFailed)
return false
}
resp, err := c.Auth.Authorize(ctx, &auth.AuthorizationRequest{
Expand Down
Loading