Skip to content
Open
77 changes: 77 additions & 0 deletions api/admin/v1/admin.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
syntax = "proto3";

package admin.v1;

option go_package = "github.com/ozontech/seq-ui/pkg/admin/v1;admin";

service AdminService {
rpc CreateRole(CreateRoleRequest) returns (CreateRoleResponse);

rpc AddUsersToRole(AddUsersToRoleRequest) returns (AddUsersToRoleResponse);

rpc GetRoles(GetRolesRequest) returns (GetRolesResponse);

rpc GetRole(GetRoleRequest) returns (GetRoleResponse);

rpc UpdateRole(UpdateRoleRequest) returns (UpdateRoleResponse);

rpc DeleteRole(DeleteRoleRequest) returns (DeleteRoleResponse);
}

message Role {
int32 id = 1;
string name = 2;
repeated uint64 permissions = 3;
}

message CreateRoleRequest {
string name = 1;
repeated uint64 permissions = 2;
}

message CreateRoleResponse {
int32 role_id = 1;
}

message AddUsersToRoleRequest {
int32 role_id = 1;
repeated string usernames = 2;
}

message AddUsersToRoleResponse {}

message GetRolesRequest {}

message GetRolesResponse {
message Permission {
uint64 value = 1;
string name = 2;
optional string description = 3;
}

repeated Role roles = 1;
repeated Permission available_permissions = 2;
}

message GetRoleRequest {
int32 id = 1;
}

message GetRoleResponse {
repeated string usernames = 1;
}

message UpdateRoleRequest {
int32 id = 1;
optional string name = 2;
repeated uint64 permissions = 3;
}

message UpdateRoleResponse {}

message DeleteRoleRequest {
int32 id = 1;
optional int32 replacement_role_id = 2;
}

message DeleteRoleResponse {}
5 changes: 2 additions & 3 deletions api/userprofile/v1/userprofile.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ message GetUserProfileResponse {
string timezone = 1;
string onboarding_version = 2;
LogColumns log_columns = 3;
optional int32 role_id = 4;
}

message UpdateUserProfileRequest {
Expand All @@ -36,7 +37,6 @@ message UpdateUserProfileRequest {

message UpdateUserProfileResponse {}


message GetFavoriteQueriesRequest {}

message GetFavoriteQueriesResponse {
Expand Down Expand Up @@ -66,7 +66,6 @@ message DeleteFavoriteQueryRequest {

message DeleteFavoriteQueryResponse {}


message GetDashboardsRequest {}

message GetDashboardsResponse {
Expand Down Expand Up @@ -109,4 +108,4 @@ message DeleteDashboardRequest {
string uuid = 1;
}

message DeleteDashboardResponse {}
message DeleteDashboardResponse {}
8 changes: 7 additions & 1 deletion cmd/seq-ui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
"github.com/joho/godotenv"
"github.com/ozontech/seq-ui/internal/api"
admin_v1 "github.com/ozontech/seq-ui/internal/api/admin/v1"
dashboards_v1 "github.com/ozontech/seq-ui/internal/api/dashboards/v1"
errorgroups_v1 "github.com/ozontech/seq-ui/internal/api/errorgroups/v1"
massexport_v1 "github.com/ozontech/seq-ui/internal/api/massexport/v1"
Expand Down Expand Up @@ -152,6 +153,7 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar {
var (
asyncSearchesService *asyncsearches.Service
p *profiles.Profiles
adminV1 *admin_v1.Admin
userProfileV1 *userprofile_v1.UserProfile
dashboardsV1 *dashboards_v1.Dashboards
)
Expand All @@ -164,6 +166,10 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar {
dashboardsV1 = dashboards_v1.New(svc, p)

asyncSearchesService = asyncsearches.New(ctx, repo, defaultClient, cfg.Handlers.AsyncSearch)

if cfg.Handlers.Admin != nil {
adminV1 = admin_v1.New(svc, cfg.Handlers.Admin)
}
}

seqApiV1 := seqapi_v1.New(cfg.Handlers.SeqAPI, seqDBClients, inmemWithRedisCache, redisCache, asyncSearchesService, p)
Expand All @@ -182,7 +188,7 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar {
errorGroupsV1 = errorgroups_v1.New(svc)
}

return api.NewRegistrar(seqApiV1, userProfileV1, dashboardsV1, massExportV1, errorGroupsV1)
return api.NewRegistrar(adminV1, seqApiV1, userProfileV1, dashboardsV1, massExportV1, errorGroupsV1)
}

func initSeqDBClients(ctx context.Context, cfg config.Config) (map[string]seqdb.Client, error) {
Expand Down
13 changes: 13 additions & 0 deletions docs/en/02-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ handlers:
error_groups:
mass_export:
async_search:
admin:
```

### SeqAPI
Expand Down Expand Up @@ -829,6 +830,18 @@ Configuration for async search request.

Maximum length of `request.query` in async searches list responses. Requests exceeding the limit will be truncated to it

### Admin

**`admin`** *`Admin`* *`optional`*

Configuration for `/admin` API.

`Admin` fields:

+ **`super_users`** *`[]string`* *`required`*

List of users with full access to admin features.

## Tracing

The tracing configuration is set through environment variables.
Expand Down
15 changes: 14 additions & 1 deletion docs/ru/02-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ handlers:
error_groups:
mass_export:
async_search:
admin:
```

### SeqAPI
Expand All @@ -515,7 +516,7 @@ handlers:

Конфигурация `/seqapi` API.

`SeqAPI` fields:
Поля `SeqAPI`:

+ **`max_search_limit`** *`int`* *`default=0`*

Expand Down Expand Up @@ -829,6 +830,18 @@ handlers:

Максимальная длина `request.query` в ответе списка отложенных запросов. Запросы, превышающие лимит, будут обрезаны до этого значения.

### Admin

**`admin`** *`Admin`* *`optional`*

Конфигурация `/admin` API.

Поля `Admin`:

+ **`super_users`** *`[]string`* *`required`*

Список пользователей с полным доступом к административным функциям.

## Tracing

Конфигурация трейсинга задается переменными окружения.
Expand Down
29 changes: 29 additions & 0 deletions internal/api/admin/v1/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package admin_v1

import (
"github.com/go-chi/chi/v5"
grpc_api "github.com/ozontech/seq-ui/internal/api/admin/v1/grpc"
http_api "github.com/ozontech/seq-ui/internal/api/admin/v1/http"
"github.com/ozontech/seq-ui/internal/app/config"
"github.com/ozontech/seq-ui/internal/pkg/service"
)

type Admin struct {
grpcAPI *grpc_api.API
httpAPI *http_api.API
}

func New(svc service.Service, cfg *config.Admin) *Admin {
return &Admin{
grpcAPI: grpc_api.New(svc, cfg),
httpAPI: http_api.New(svc, cfg),
}
}

func (a *Admin) GRPCServer() *grpc_api.API {
return a.grpcAPI
}

func (a *Admin) HTTPRouter() chi.Router {
return a.httpAPI.Router()
}
71 changes: 71 additions & 0 deletions internal/api/admin/v1/grpc/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package grpc

import (
"context"
"strings"

"github.com/ozontech/seq-ui/internal/app/config"
"github.com/ozontech/seq-ui/internal/app/types"
"github.com/ozontech/seq-ui/internal/pkg/service"
"github.com/ozontech/seq-ui/pkg/admin/v1"
)

var grpcRoutePermissions = map[string]uint64{
"Role": service.PermissionManageRoles,
}

type API struct {
admin.UnimplementedAdminServiceServer

service service.Service
availablePermissions []*admin.GetRolesResponse_Permission
superUsers map[string]struct{}
}

func New(svc service.Service, cfg *config.Admin) *API {
su := make(map[string]struct{}, len(cfg.SuperUsers))
for _, user := range cfg.SuperUsers {
su[user] = struct{}{}
}
return &API{
service: svc,
availablePermissions: availablePermissionsToProto(svc.GetAvailablePermissions()),
superUsers: su,
}
}

func (a *API) authorize(ctx context.Context, method string) error {
username, err := types.GetUserKey(ctx)
if err != nil {
return types.ErrUnauthenticated
}

if _, ok := a.superUsers[username]; ok {
return nil
}

requiredPermission, ok := matchGRPCRoutePermissions(method)
if !ok {
return types.ErrPermissionDenied
}

resp, err := a.service.GetUserPermissions(ctx, types.GetUserPermissionsRequest{Username: username})
if err != nil {
return types.ErrPermissionDenied
}

if resp.Permissions&requiredPermission == 0 {
return types.ErrPermissionDenied
}

return nil
}

func matchGRPCRoutePermissions(method string) (uint64, bool) {
for keyword, permission := range grpcRoutePermissions {
if strings.Contains(method, keyword) {
return permission, true
}
}
return 0, false
}
Loading
Loading