mirror of
https://github.com/AlistGo/alist.git
synced 2025-11-25 03:15:10 +08:00
feat: enhance permission control and label management (#9215)
* 标签管理 * pr检查优化 * feat(role): Implement role management functionality - Add role management routes in `server/router.go` for listing, getting, creating, updating, and deleting roles - Introduce `initRoles()` in `internal/bootstrap/data/data.go` for initializing roles during bootstrap - Create `internal/op/role.go` to handle role operations including caching and singleflight - Implement role handler functions in `server/handles/role.go` for API responses - Define database operations for roles in `internal/db/role.go` - Extend `internal/db/db.go` for role model auto-migration - Design `internal/model/role.go` to represent role structure with ID, name, description, base path, and permissions - Initialize default roles (`admin` and `guest`) in `internal/bootstrap/data/role.go` during startup * refactor(user roles): Support multiple roles for users - Change the `Role` field type from `int` to `[]int` in `drivers/alist_v3/types.go` and `drivers/quqi/types.go`. - Update the `Role` field in `internal/model/user.go` to use a new `Roles` type with JSON and database support. - Modify `IsGuest` and `IsAdmin` methods to check for roles using `Contains` method. - Update `GetUserByRole` method in `internal/db/user.go` to handle multiple roles. - Add `roles.go` to define a new `Roles` type with JSON marshalling and scanning capabilities. - Adjust code in `server/handles/user.go` to compare roles with `utils.SliceEqual`. - Change role initialization for users in `internal/bootstrap/data/dev.go` and `internal/bootstrap/data/user.go`. - Update `Role` handling in `server/handles/task.go`, `server/handles/ssologin.go`, and `server/handles/ldap_login.go`. * feat(user/role): Add path limit check for user and role permissions - Add new permission bit for checking path limits in `user.go` - Implement `CheckPathLimit` method in `User` struct to validate path access - Modify `JoinPath` method in `User` to enforce path limit checks - Update `role.go` to include path limit logic in `Role` struct - Document new permission bit in `Role` and `User` comments for clarity * feat(permission): Add role-based permission handling - Introduce `role_perm.go` for managing user permissions based on roles. - Implement `HasPermission` and `MergeRolePermissions` functions. - Update `webdav.go` to utilize role-based permissions instead of direct user checks. - Modify `fsup.go` to integrate `CanAccessWithRoles` function. - Refactor `fsread.go` to use `common.HasPermission` for permission validation. - Adjust `fsmanage.go` for role-based access control checks. - Enhance `ftp.go` and `sftp.go` to manage FTP access via roles. - Update `fsbatch.go` to employ `MergeRolePermissions` for batch operations. - Replace direct user permission checks with role-based permission handling across various modules. * refactor(user): Replace integer role values with role IDs - Change `GetAdmin()` and `GetGuest()` functions to retrieve role by name and use role ID. - Add patch for version `v3.45.2` to convert legacy integer roles to role IDs. - Update `dev.go` and `user.go` to use role IDs instead of integer values for roles. - Remove redundant code in `role.go` related to guest role creation. - Modify `ssologin.go` and `ldap_login.go` to set user roles to nil instead of using integer roles. - Introduce `convert_roles.go` to handle conversion of legacy roles and ensure role existence in the database. * feat(role_perm): implement support for multiple base paths for roles - Modify role permission checks to support multiple base paths - Update role creation and update functions to handle multiple base paths - Add migration script to convert old base_path to base_paths - Define new Paths type for handling multiple paths in the model - Adjust role model to replace BasePath with BasePaths - Update existing patches to handle roles with multiple base paths - Update bootstrap data to reflect the new base_paths field * feat(role): Restrict modifications to default roles (admin and guest) - Add validation to prevent changes to "admin" and "guest" roles in `UpdateRole` and `DeleteRole` functions. - Introduce `ErrChangeDefaultRole` error in `internal/errs/role.go` to standardize error messaging. - Update role-related API handlers in `server/handles/role.go` to enforce the new restriction. - Enhance comments in `internal/bootstrap/data/role.go` to clarify the significance of default roles. - Ensure consistent error responses for unauthorized role modifications across the application. * 🔄 **refactor(role): Enhance role permission handling** - Replaced `BasePaths` with `PermissionPaths` in `Role` struct for better permission granularity. - Introduced JSON serialization for `PermissionPaths` using `RawPermission` field in `Role` struct. - Implemented `BeforeSave` and `AfterFind` GORM hooks for handling `PermissionPaths` serialization. - Refactored permission calculation logic in `role_perm.go` to work with `PermissionPaths`. - Updated role creation logic to initialize `PermissionPaths` for `admin` and `guest` roles. - Removed deprecated `CheckPathLimit` method from `Role` struct. * fix(model/user/role): update permission settings for admin and role - Change `RawPermission` field in `role.go` to hide JSON representation - Update `Permission` field in `user.go` to `0xFFFF` for full access - Modify `PermissionScopes` in `role.go` to `0xFFFF` for enhanced permissions * 🔒 feat(role-permissions): Enhance role-based access control - Introduce `canReadPathByRole` function in `role_perm.go` to verify path access based on user roles - Modify `CanAccessWithRoles` to include role-based path read check - Add `RoleNames` and `Permissions` to `UserResp` struct in `auth.go` for enhanced user role and permission details - Implement role details aggregation in `auth.go` to populate `RoleNames` and `Permissions` - Update `User` struct in `user.go` to include `RolesDetail` for more detailed role information - Enhance middleware in `auth.go` to load and verify detailed role information for users - Move `guest` user initialization logic in `user.go` to improve code organization and avoid repetition * 🔒 fix(permissions): Add permission checks for archive operations - Add `MergeRolePermissions` and `HasPermission` checks to validate user access for reading archives - Ensure users have `PermReadArchives` before proceeding with `GetNearestMeta` in specific archive paths - Implement permission checks for decompress operations, requiring `PermDecompress` for source paths - Return `PermissionDenied` errors with 403 status if user lacks necessary permissions * 🔒 fix(server): Add permission check for offline download - Add permission merging logic for user roles - Check user has permission for offline download addition - Return error response with "permission denied" if check fails * ✨ feat(role-permission): Implement path-based role permission checks - Add `CheckPathLimitWithRoles` function to validate access based on `PermPathLimit` permission. - Integrate `CheckPathLimitWithRoles` in `offline_download` to enforce path-based access control. - Apply `CheckPathLimitWithRoles` across file system management operations (e.g., creation, movement, deletion). - Ensure `CheckPathLimitWithRoles` is invoked for batch operations and archive-related actions. - Update error handling to return `PermissionDenied` if the path validation fails. - Import `errs` package in `offline_download` for consistent error responses. * ✨ feat(role-permission): Implement path-based role permission checks - Add `CheckPathLimitWithRoles` function to validate access based on `PermPathLimit` permission. - Integrate `CheckPathLimitWithRoles` in `offline_download` to enforce path-based access control. - Apply `CheckPathLimitWithRoles` across file system management operations (e.g., creation, movement, deletion). - Ensure `CheckPathLimitWithRoles` is invoked for batch operations and archive-related actions. - Update error handling to return `PermissionDenied` if the path validation fails. - Import `errs` package in `offline_download` for consistent error responses. * ♻️ refactor(access-control): Update access control logic to use role-based checks - Remove deprecated logic from `CanAccess` function in `check.go`, replacing it with `CanAccessWithRoles` for improved role-based access control. - Modify calls in `search.go` to use `CanAccessWithRoles` for more precise handling of permissions. - Update `fsread.go` to utilize `CanAccessWithRoles`, ensuring accurate access validation based on user roles. - Simplify import statements in `check.go` by removing unused packages to clean up the codebase. * ✨ feat(fs): Improve visibility logic for hidden files - Import `server/common` package to handle permissions more robustly - Update `whetherHide` function to use `MergeRolePermissions` for user-specific path permissions - Replace direct user checks with `HasPermission` for `PermSeeHides` - Enhance logic to ensure `nil` user cases are handled explicitly * 标签管理 * feat(db/auth/user): Enhance role handling and clean permission paths - Comment out role modification checks in `server/handles/user.go` to allow flexible role changes. - Improve permission path handling in `server/handles/auth.go` by normalizing and deduplicating paths. - Introduce `addedPaths` map in `CurrentUser` to prevent duplicate permissions. * feat(storage/db): Implement role permissions path prefix update - Add `UpdateRolePermissionsPathPrefix` function in `role.go` to update role permissions paths. - Modify `storage.go` to call the new function when the mount path is renamed. - Introduce path cleaning and prefix matching logic for accurate path updates. - Ensure roles are updated only if their permission scopes are modified. - Handle potential errors with informative messages during database operations. * feat(role-migration): Implement role conversion and introduce NEWGENERAL role - Add `NEWGENERAL` to the roles enumeration in `user.go` - Create new file `convert_role.go` for migrating legacy roles to new model - Implement `ConvertLegacyRoles` function to handle role conversion with permission scopes - Add `convert_role.go` patch to `all.go` under version `v3.46.0` * feat(role/auth): Add role retrieval by user ID and update path prefixes - Add `GetRolesByUserID` function for efficient role retrieval by user ID - Implement `UpdateUserBasePathPrefix` to update user base paths - Modify `UpdateRolePermissionsPathPrefix` to return modified role IDs - Update `auth.go` middleware to use the new role retrieval function - Refresh role and user caches upon path prefix updates to maintain consistency --------- Co-authored-by: Leslie-Xy <540049476@qq.com>
This commit is contained in:
@@ -56,7 +56,7 @@ func (d *AListV3) Init(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.Data.Role == model.GUEST {
|
if utils.SliceContains(resp.Data.Role, model.GUEST) {
|
||||||
u := d.Address + "/api/public/settings"
|
u := d.Address + "/api/public/settings"
|
||||||
res, err := base.RestyClient.R().Get(u)
|
res, err := base.RestyClient.R().Get(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ type MeResp struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
BasePath string `json:"base_path"`
|
BasePath string `json:"base_path"`
|
||||||
Role int `json:"role"`
|
Role []int `json:"role"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Permission int `json:"permission"`
|
Permission int `json:"permission"`
|
||||||
SsoId string `json:"sso_id"`
|
SsoId string `json:"sso_id"`
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ type Group struct {
|
|||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsAdministrator int `json:"is_administrator"`
|
IsAdministrator int `json:"is_administrator"`
|
||||||
Role int `json:"role"`
|
Role []int `json:"role"`
|
||||||
Avatar string `json:"avatar_url"`
|
Avatar string `json:"avatar_url"`
|
||||||
IsStick int `json:"is_stick"`
|
IsStick int `json:"is_stick"`
|
||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package data
|
|||||||
import "github.com/alist-org/alist/v3/cmd/flags"
|
import "github.com/alist-org/alist/v3/cmd/flags"
|
||||||
|
|
||||||
func InitData() {
|
func InitData() {
|
||||||
|
initRoles()
|
||||||
initUser()
|
initUser()
|
||||||
initSettings()
|
initSettings()
|
||||||
initTasks()
|
initTasks()
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func initDevData() {
|
|||||||
Username: "Noah",
|
Username: "Noah",
|
||||||
Password: "hsu",
|
Password: "hsu",
|
||||||
BasePath: "/data",
|
BasePath: "/data",
|
||||||
Role: 0,
|
Role: nil,
|
||||||
Permission: 512,
|
Permission: 512,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
52
internal/bootstrap/data/role.go
Normal file
52
internal/bootstrap/data/role.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
// initRoles creates the default admin and guest roles if missing.
|
||||||
|
// These roles are essential and must not be modified or removed.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initRoles() {
|
||||||
|
guestRole, err := op.GetRoleByName("guest")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
guestRole = &model.Role{
|
||||||
|
ID: uint(model.GUEST),
|
||||||
|
Name: "guest",
|
||||||
|
Description: "Guest",
|
||||||
|
PermissionScopes: []model.PermissionEntry{
|
||||||
|
{Path: "/", Permission: 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := op.CreateRole(guestRole); err != nil {
|
||||||
|
utils.Log.Fatalf("[init role] Failed to create guest role: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Fatalf("[init role] Failed to get guest role: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = op.GetRoleByName("admin")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
adminRole := &model.Role{
|
||||||
|
ID: uint(model.ADMIN),
|
||||||
|
Name: "admin",
|
||||||
|
Description: "Administrator",
|
||||||
|
PermissionScopes: []model.PermissionEntry{
|
||||||
|
{Path: "/", Permission: 0xFFFF},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := op.CreateRole(adminRole); err != nil {
|
||||||
|
utils.Log.Fatalf("[init role] Failed to create admin role: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Fatalf("[init role] Failed to get admin role: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
"github.com/alist-org/alist/v3/internal/db"
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@@ -14,45 +14,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initUser() {
|
func initUser() {
|
||||||
admin, err := op.GetAdmin()
|
|
||||||
adminPassword := random.String(8)
|
|
||||||
envpass := os.Getenv("ALIST_ADMIN_PASSWORD")
|
|
||||||
if flags.Dev {
|
|
||||||
adminPassword = "admin"
|
|
||||||
} else if len(envpass) > 0 {
|
|
||||||
adminPassword = envpass
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
salt := random.String(16)
|
|
||||||
admin = &model.User{
|
|
||||||
Username: "admin",
|
|
||||||
Salt: salt,
|
|
||||||
PwdHash: model.TwoHashPwd(adminPassword, salt),
|
|
||||||
Role: model.ADMIN,
|
|
||||||
BasePath: "/",
|
|
||||||
Authn: "[]",
|
|
||||||
// 0(can see hidden) - 7(can remove) & 12(can read archives) - 13(can decompress archives)
|
|
||||||
Permission: 0x30FF,
|
|
||||||
}
|
|
||||||
if err := op.CreateUser(admin); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
utils.Log.Infof("Successfully created the admin user and the initial password is: %s", adminPassword)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
utils.Log.Fatalf("[init user] Failed to get admin user: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guest, err := op.GetGuest()
|
guest, err := op.GetGuest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
salt := random.String(16)
|
salt := random.String(16)
|
||||||
|
guestRole, _ := op.GetRoleByName("guest")
|
||||||
guest = &model.User{
|
guest = &model.User{
|
||||||
Username: "guest",
|
Username: "guest",
|
||||||
PwdHash: model.TwoHashPwd("guest", salt),
|
PwdHash: model.TwoHashPwd("guest", salt),
|
||||||
Salt: salt,
|
Salt: salt,
|
||||||
Role: model.GUEST,
|
Role: model.Roles{int(guestRole.ID)},
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
Permission: 0,
|
Permission: 0,
|
||||||
Disabled: true,
|
Disabled: true,
|
||||||
@@ -65,4 +36,35 @@ func initUser() {
|
|||||||
utils.Log.Fatalf("[init user] Failed to get guest user: %v", err)
|
utils.Log.Fatalf("[init user] Failed to get guest user: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
admin, err := op.GetAdmin()
|
||||||
|
adminPassword := random.String(8)
|
||||||
|
envpass := os.Getenv("ALIST_ADMIN_PASSWORD")
|
||||||
|
if flags.Dev {
|
||||||
|
adminPassword = "admin"
|
||||||
|
} else if len(envpass) > 0 {
|
||||||
|
adminPassword = envpass
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
salt := random.String(16)
|
||||||
|
adminRole, _ := op.GetRoleByName("admin")
|
||||||
|
admin = &model.User{
|
||||||
|
Username: "admin",
|
||||||
|
Salt: salt,
|
||||||
|
PwdHash: model.TwoHashPwd(adminPassword, salt),
|
||||||
|
Role: model.Roles{int(adminRole.ID)},
|
||||||
|
BasePath: "/",
|
||||||
|
Authn: "[]",
|
||||||
|
// 0(can see hidden) - 7(can remove) & 12(can read archives) - 13(can decompress archives)
|
||||||
|
Permission: 0xFFFF,
|
||||||
|
}
|
||||||
|
if err := op.CreateUser(admin); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
utils.Log.Infof("Successfully created the admin user and the initial password is: %s", adminPassword)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Fatalf("[init user] Failed to get admin user: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_24_0"
|
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_24_0"
|
||||||
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_32_0"
|
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_32_0"
|
||||||
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_41_0"
|
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_41_0"
|
||||||
|
"github.com/alist-org/alist/v3/internal/bootstrap/patch/v3_46_0"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VersionPatches struct {
|
type VersionPatches struct {
|
||||||
@@ -32,4 +33,10 @@ var UpgradePatches = []VersionPatches{
|
|||||||
v3_41_0.GrantAdminPermissions,
|
v3_41_0.GrantAdminPermissions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Version: "v3.46.0",
|
||||||
|
Patches: []func(){
|
||||||
|
v3_46_0.ConvertLegacyRoles,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
129
internal/bootstrap/patch/v3_46_0/convert_role.go
Normal file
129
internal/bootstrap/patch/v3_46_0/convert_role.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package v3_46_0
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertLegacyRoles migrates old integer role values to a new role model with permission scopes.
|
||||||
|
func ConvertLegacyRoles() {
|
||||||
|
guestRole, err := op.GetRoleByName("guest")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
guestRole = &model.Role{
|
||||||
|
ID: uint(model.GUEST),
|
||||||
|
Name: "guest",
|
||||||
|
Description: "Guest",
|
||||||
|
PermissionScopes: []model.PermissionEntry{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Permission: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err = op.CreateRole(guestRole); err != nil {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to create guest role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to get guest role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adminRole, err := op.GetRoleByName("admin")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
adminRole = &model.Role{
|
||||||
|
ID: uint(model.ADMIN),
|
||||||
|
Name: "admin",
|
||||||
|
Description: "Administrator",
|
||||||
|
PermissionScopes: []model.PermissionEntry{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Permission: 0x33FF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err = op.CreateRole(adminRole); err != nil {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to create admin role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to get admin role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generalRole, err := op.GetRoleByName("general")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
generalRole = &model.Role{
|
||||||
|
ID: uint(model.NEWGENERAL),
|
||||||
|
Name: "general",
|
||||||
|
Description: "General User",
|
||||||
|
PermissionScopes: []model.PermissionEntry{
|
||||||
|
{
|
||||||
|
Path: "/",
|
||||||
|
Permission: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err = op.CreateRole(generalRole); err != nil {
|
||||||
|
utils.Log.Errorf("[convert roles] failed create general role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
utils.Log.Errorf("[convert roles] failed get general role: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
users, _, err := op.GetUsers(1, -1)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to get users: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range users {
|
||||||
|
user := users[i]
|
||||||
|
if user.Role == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changed := false
|
||||||
|
var roles model.Roles
|
||||||
|
for _, r := range user.Role {
|
||||||
|
switch r {
|
||||||
|
case model.ADMIN:
|
||||||
|
roles = append(roles, int(adminRole.ID))
|
||||||
|
if int(adminRole.ID) != r {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
case model.GUEST:
|
||||||
|
roles = append(roles, int(guestRole.ID))
|
||||||
|
if int(guestRole.ID) != r {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
case model.GENERAL:
|
||||||
|
roles = append(roles, int(generalRole.ID))
|
||||||
|
if int(generalRole.ID) != r {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
roles = append(roles, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
user.Role = roles
|
||||||
|
if err := db.UpdateUser(&user); err != nil {
|
||||||
|
utils.Log.Errorf("[convert roles] failed to update user %s: %v", user.Username, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Log.Infof("[convert roles] completed role conversion for %d users", len(users))
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ var db *gorm.DB
|
|||||||
|
|
||||||
func Init(d *gorm.DB) {
|
func Init(d *gorm.DB) {
|
||||||
db = d
|
db = d
|
||||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey))
|
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.Role), new(model.Label), new(model.LabelFileBinDing), new(model.ObjFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migrate database: %s", err.Error())
|
log.Fatalf("failed migrate database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
79
internal/db/label.go
Normal file
79
internal/db/label.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLabels Get all label from database order by id
|
||||||
|
func GetLabels(pageIndex, pageSize int) ([]model.Label, int64, error) {
|
||||||
|
labelDB := db.Model(&model.Label{})
|
||||||
|
var count int64
|
||||||
|
if err := labelDB.Count(&count).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get label count")
|
||||||
|
}
|
||||||
|
var labels []model.Label
|
||||||
|
if err := labelDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&labels).Error; err != nil {
|
||||||
|
return nil, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return labels, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelById Get Label by id, used to update label usually
|
||||||
|
func GetLabelById(id uint) (*model.Label, error) {
|
||||||
|
var label model.Label
|
||||||
|
label.ID = id
|
||||||
|
if err := db.First(&label).Error; err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return &label, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLabel just insert label to database
|
||||||
|
func CreateLabel(label model.Label) (uint, error) {
|
||||||
|
label.CreateTime = time.Now()
|
||||||
|
err := errors.WithStack(db.Create(&label).Error)
|
||||||
|
if err != nil {
|
||||||
|
return label.ID, errors.WithMessage(err, "failed create label in database")
|
||||||
|
}
|
||||||
|
return label.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLabel just update storage in database
|
||||||
|
func UpdateLabel(label *model.Label) (*model.Label, error) {
|
||||||
|
label.CreateTime = time.Now()
|
||||||
|
_, err := GetLabelById(label.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed get old label")
|
||||||
|
}
|
||||||
|
err = errors.WithStack(db.Save(label).Error)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed create label in database")
|
||||||
|
}
|
||||||
|
return label, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLabelById just delete label from database by id
|
||||||
|
func DeleteLabelById(id uint) error {
|
||||||
|
return errors.WithStack(db.Delete(&model.Label{}, id).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelByIds Get label from database order by ids
|
||||||
|
func GetLabelByIds(ids []uint) ([]model.Label, error) {
|
||||||
|
labelDB := db.Model(&model.Label{})
|
||||||
|
var labels []model.Label
|
||||||
|
if err := labelDB.Where(ids).Find(&labels).Error; err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelByName Get Label by name
|
||||||
|
func GetLabelByName(name string) bool {
|
||||||
|
var label model.Label
|
||||||
|
result := db.Where("name = ?", name).First(&label)
|
||||||
|
exists := !errors.Is(result.Error, gorm.ErrRecordNotFound)
|
||||||
|
return exists
|
||||||
|
}
|
||||||
56
internal/db/label_file_binding.go
Normal file
56
internal/db/label_file_binding.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLabelIds Get all label_ids from database order by file_name
|
||||||
|
func GetLabelIds(userId uint, fileName string) ([]uint, error) {
|
||||||
|
labelFileBinDingDB := db.Model(&model.LabelFileBinDing{})
|
||||||
|
var labelIds []uint
|
||||||
|
if err := labelFileBinDingDB.Where("file_name = ?", fileName).Where("user_id = ?", userId).Pluck("label_id", &labelIds).Error; err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return labelIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabelFileBinDing(fileName string, labelId, userId uint) error {
|
||||||
|
var labelFileBinDing model.LabelFileBinDing
|
||||||
|
labelFileBinDing.UserId = userId
|
||||||
|
labelFileBinDing.LabelId = labelId
|
||||||
|
labelFileBinDing.FileName = fileName
|
||||||
|
labelFileBinDing.CreateTime = time.Now()
|
||||||
|
err := errors.WithStack(db.Create(&labelFileBinDing).Error)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed create label in database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabelFileBinDingByLabelIdExists Get Label by label_id, used to del label usually
|
||||||
|
func GetLabelFileBinDingByLabelIdExists(labelId, userId uint) bool {
|
||||||
|
var labelFileBinDing model.LabelFileBinDing
|
||||||
|
result := db.Where("label_id = ?", labelId).Where("user_id = ?", userId).First(&labelFileBinDing)
|
||||||
|
exists := !errors.Is(result.Error, gorm.ErrRecordNotFound)
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLabelFileBinDingByFileName used to del usually
|
||||||
|
func DelLabelFileBinDingByFileName(userId uint, fileName string) error {
|
||||||
|
return errors.WithStack(db.Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinDing{}).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLabelFileBinDingById used to del usually
|
||||||
|
func DelLabelFileBinDingById(labelId, userId uint, fileName string) error {
|
||||||
|
return errors.WithStack(db.Where("label_id = ?", labelId).Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinDing{}).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLabelFileBinDingByLabelId(labelIds []uint, userId uint) (result []model.LabelFileBinDing, err error) {
|
||||||
|
if err := db.Where("label_id in (?)", labelIds).Where("user_id = ?", userId).Find(&result).Error; err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
31
internal/db/obj_file.go
Normal file
31
internal/db/obj_file.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFileByNameExists Get file by name
|
||||||
|
func GetFileByNameExists(name string) bool {
|
||||||
|
var label model.ObjFile
|
||||||
|
result := db.Where("name = ?", name).First(&label)
|
||||||
|
exists := !errors.Is(result.Error, gorm.ErrRecordNotFound)
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileByName Get file by name
|
||||||
|
func GetFileByName(name string, userId uint) (objFile model.ObjFile, err error) {
|
||||||
|
if err = db.Where("name = ?", name).Where("user_id = ?", userId).First(&objFile).Error; err != nil {
|
||||||
|
return objFile, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return objFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateObjFile(obj model.ObjFile) error {
|
||||||
|
err := errors.WithStack(db.Create(&obj).Error)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed create file in database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
79
internal/db/role.go
Normal file
79
internal/db/role.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRole(id uint) (*model.Role, error) {
|
||||||
|
var r model.Role
|
||||||
|
if err := db.First(&r, id).Error; err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed get role")
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRoleByName(name string) (*model.Role, error) {
|
||||||
|
r := model.Role{Name: name}
|
||||||
|
if err := db.Where(r).First(&r).Error; err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed get role")
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRoles(pageIndex, pageSize int) (roles []model.Role, count int64, err error) {
|
||||||
|
roleDB := db.Model(&model.Role{})
|
||||||
|
if err = roleDB.Count(&count).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get roles count")
|
||||||
|
}
|
||||||
|
if err = roleDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&roles).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get find roles")
|
||||||
|
}
|
||||||
|
return roles, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRole(r *model.Role) error {
|
||||||
|
return errors.WithStack(db.Create(r).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRole(r *model.Role) error {
|
||||||
|
return errors.WithStack(db.Save(r).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRole(id uint) error {
|
||||||
|
return errors.WithStack(db.Delete(&model.Role{}, id).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRolePermissionsPathPrefix(oldPath, newPath string) ([]uint, error) {
|
||||||
|
var roles []model.Role
|
||||||
|
var modifiedRoleIDs []uint
|
||||||
|
|
||||||
|
if err := db.Find(&roles).Error; err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to load roles")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, role := range roles {
|
||||||
|
updated := false
|
||||||
|
for i, entry := range role.PermissionScopes {
|
||||||
|
entryPath := path.Clean(entry.Path)
|
||||||
|
oldPathClean := path.Clean(oldPath)
|
||||||
|
|
||||||
|
if entryPath == oldPathClean {
|
||||||
|
role.PermissionScopes[i].Path = newPath
|
||||||
|
updated = true
|
||||||
|
} else if strings.HasPrefix(entryPath, oldPathClean+"/") {
|
||||||
|
role.PermissionScopes[i].Path = newPath + entryPath[len(oldPathClean):]
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
if err := UpdateRole(&role); err != nil {
|
||||||
|
return nil, errors.WithMessagef(err, "failed to update role ID %d", role.ID)
|
||||||
|
}
|
||||||
|
modifiedRoleIDs = append(modifiedRoleIDs, role.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modifiedRoleIDs, nil
|
||||||
|
}
|
||||||
@@ -2,19 +2,26 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetUserByRole(role int) (*model.User, error) {
|
func GetUserByRole(role int) (*model.User, error) {
|
||||||
user := model.User{Role: role}
|
var users []model.User
|
||||||
if err := db.Where(user).Take(&user).Error; err != nil {
|
if err := db.Find(&users).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &user, nil
|
for i := range users {
|
||||||
|
if users[i].Role.Contains(role) {
|
||||||
|
return &users[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserByName(username string) (*model.User, error) {
|
func GetUserByName(username string) (*model.User, error) {
|
||||||
@@ -100,3 +107,36 @@ func RemoveAuthn(u *model.User, id string) error {
|
|||||||
}
|
}
|
||||||
return UpdateAuthn(u.ID, string(res))
|
return UpdateAuthn(u.ID, string(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateUserBasePathPrefix(oldPath, newPath string) ([]string, error) {
|
||||||
|
var users []model.User
|
||||||
|
var modifiedUsernames []string
|
||||||
|
|
||||||
|
if err := db.Find(&users).Error; err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed to load users")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldPathClean := path.Clean(oldPath)
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
basePath := path.Clean(user.BasePath)
|
||||||
|
updated := false
|
||||||
|
|
||||||
|
if basePath == oldPathClean {
|
||||||
|
user.BasePath = newPath
|
||||||
|
updated = true
|
||||||
|
} else if strings.HasPrefix(basePath, oldPathClean+"/") {
|
||||||
|
user.BasePath = newPath + basePath[len(oldPathClean):]
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
if err := UpdateUser(&user); err != nil {
|
||||||
|
return nil, errors.WithMessagef(err, "failed to update user ID %d", user.ID)
|
||||||
|
}
|
||||||
|
modifiedUsernames = append(modifiedUsernames, user.Username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiedUsernames, nil
|
||||||
|
}
|
||||||
|
|||||||
7
internal/errs/role.go
Normal file
7
internal/errs/role.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrChangeDefaultRole = errors.New("cannot modify admin or guest role")
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -45,8 +46,13 @@ func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func whetherHide(user *model.User, meta *model.Meta, path string) bool {
|
func whetherHide(user *model.User, meta *model.Meta, path string) bool {
|
||||||
// if is admin, don't hide
|
// if user is nil, don't hide
|
||||||
if user == nil || user.CanSeeHides() {
|
if user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, path)
|
||||||
|
// if user has see-hides permission, don't hide
|
||||||
|
if common.HasPermission(perm, common.PermSeeHides) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if meta is nil, don't hide
|
// if meta is nil, don't hide
|
||||||
|
|||||||
12
internal/model/label.go
Normal file
12
internal/model/label.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||||
|
Type int `json:"type"` // use to type
|
||||||
|
Name string `json:"name"` // use to name
|
||||||
|
Description string `json:"description"` // use to description
|
||||||
|
BgColor string `json:"bg_color"` // use to bg_color
|
||||||
|
CreateTime time.Time `json:"create_time"`
|
||||||
|
}
|
||||||
11
internal/model/label_file_binding.go
Normal file
11
internal/model/label_file_binding.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type LabelFileBinDing struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||||
|
UserId uint `json:"user_id"` // use to user_id
|
||||||
|
LabelId uint `json:"label_id"` // use to label_id
|
||||||
|
FileName string `json:"file_name"` // use to file_name
|
||||||
|
CreateTime time.Time `json:"create_time"`
|
||||||
|
}
|
||||||
18
internal/model/obj_file.go
Normal file
18
internal/model/obj_file.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ObjFile struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
UserId uint `json:"user_id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
IsDir bool `json:"is_dir"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
HashInfoStr string `json:"hashinfo"`
|
||||||
|
}
|
||||||
27
internal/model/paths.go
Normal file
27
internal/model/paths.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Paths []string
|
||||||
|
|
||||||
|
func (p Paths) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal([]string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Paths) Scan(value interface{}) error {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(v, (*[]string)(p))
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(v), (*[]string)(p))
|
||||||
|
case nil:
|
||||||
|
*p = nil
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot scan %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
internal/model/role.go
Normal file
52
internal/model/role.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PermissionEntry defines permission bitmask for a specific path.
|
||||||
|
type PermissionEntry struct {
|
||||||
|
Path string `json:"path"` // path prefix, e.g. "/admin"
|
||||||
|
Permission int32 `json:"permission"` // bitmask permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role represents a permission template which can be bound to users.
|
||||||
|
type Role struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
|
Name string `json:"name" gorm:"unique" binding:"required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
// PermissionScopes stores structured permission list and is ignored by gorm.
|
||||||
|
PermissionScopes []PermissionEntry `json:"permission_scopes" gorm:"-"`
|
||||||
|
// RawPermission is the JSON representation of PermissionScopes stored in DB.
|
||||||
|
RawPermission string `json:"-" gorm:"type:text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeSave GORM hook serializes PermissionScopes into RawPermission.
|
||||||
|
func (r *Role) BeforeSave(tx *gorm.DB) error {
|
||||||
|
if len(r.PermissionScopes) == 0 {
|
||||||
|
r.RawPermission = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bs, err := json.Marshal(r.PermissionScopes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.RawPermission = string(bs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterFind GORM hook deserializes RawPermission into PermissionScopes.
|
||||||
|
func (r *Role) AfterFind(tx *gorm.DB) error {
|
||||||
|
if r.RawPermission == "" {
|
||||||
|
r.PermissionScopes = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var scopes []PermissionEntry
|
||||||
|
if err := json.Unmarshal([]byte(r.RawPermission), &scopes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.PermissionScopes = scopes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
36
internal/model/roles.go
Normal file
36
internal/model/roles.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Roles []int
|
||||||
|
|
||||||
|
func (r Roles) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal([]int(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Roles) Scan(value interface{}) error {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(v, (*[]int)(r))
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(v), (*[]int)(r))
|
||||||
|
case nil:
|
||||||
|
*r = nil
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot scan %T", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Roles) Contains(role int) bool {
|
||||||
|
for _, v := range r {
|
||||||
|
if v == role {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -17,20 +17,22 @@ const (
|
|||||||
GENERAL = iota
|
GENERAL = iota
|
||||||
GUEST // only one exists
|
GUEST // only one exists
|
||||||
ADMIN
|
ADMIN
|
||||||
|
NEWGENERAL
|
||||||
)
|
)
|
||||||
|
|
||||||
const StaticHashSalt = "https://github.com/alist-org/alist"
|
const StaticHashSalt = "https://github.com/alist-org/alist"
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||||
Username string `json:"username" gorm:"unique" binding:"required"` // username
|
Username string `json:"username" gorm:"unique" binding:"required"` // username
|
||||||
PwdHash string `json:"-"` // password hash
|
PwdHash string `json:"-"` // password hash
|
||||||
PwdTS int64 `json:"-"` // password timestamp
|
PwdTS int64 `json:"-"` // password timestamp
|
||||||
Salt string `json:"-"` // unique salt
|
Salt string `json:"-"` // unique salt
|
||||||
Password string `json:"password"` // password
|
Password string `json:"password"` // password
|
||||||
BasePath string `json:"base_path"` // base path
|
BasePath string `json:"base_path"` // base path
|
||||||
Role int `json:"role"` // user's role
|
Role Roles `json:"role" gorm:"type:text"` // user's roles
|
||||||
Disabled bool `json:"disabled"`
|
RolesDetail []Role `json:"-" gorm:"-"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
// Determine permissions by bit
|
// Determine permissions by bit
|
||||||
// 0: can see hidden files
|
// 0: can see hidden files
|
||||||
// 1: can access without password
|
// 1: can access without password
|
||||||
@@ -46,6 +48,7 @@ type User struct {
|
|||||||
// 11: ftp/sftp write
|
// 11: ftp/sftp write
|
||||||
// 12: can read archives
|
// 12: can read archives
|
||||||
// 13: can decompress archives
|
// 13: can decompress archives
|
||||||
|
// 14: check path limit
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
SsoID string `json:"sso_id"` // unique by sso platform
|
SsoID string `json:"sso_id"` // unique by sso platform
|
||||||
@@ -53,11 +56,11 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) IsGuest() bool {
|
func (u *User) IsGuest() bool {
|
||||||
return u.Role == GUEST
|
return u.Role.Contains(GUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) IsAdmin() bool {
|
func (u *User) IsAdmin() bool {
|
||||||
return u.Role == ADMIN
|
return u.Role.Contains(ADMIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) ValidateRawPassword(password string) error {
|
func (u *User) ValidateRawPassword(password string) error {
|
||||||
@@ -137,8 +140,19 @@ func (u *User) CanDecompress() bool {
|
|||||||
return (u.Permission>>13)&1 == 1
|
return (u.Permission>>13)&1 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) CheckPathLimit() bool {
|
||||||
|
return (u.Permission>>14)&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) JoinPath(reqPath string) (string, error) {
|
func (u *User) JoinPath(reqPath string) (string, error) {
|
||||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
path, err := utils.JoinBasePath(u.BasePath, reqPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if u.CheckPathLimit() && !utils.IsSubPath(u.BasePath, path) {
|
||||||
|
return "", errs.PermissionDenied
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StaticHash(password string) string {
|
func StaticHash(password string) string {
|
||||||
|
|||||||
24
internal/op/label.go
Normal file
24
internal/op/label.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteLabelById(ctx context.Context, id, userId uint) error {
|
||||||
|
_, err := db.GetLabelById(id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get label")
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.GetLabelFileBinDingByLabelIdExists(id, userId) {
|
||||||
|
return errors.New("label have binding relationships")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the label in the database
|
||||||
|
if err := db.DeleteLabelById(id); err != nil {
|
||||||
|
return errors.WithMessage(err, "failed delete label in database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
159
internal/op/label_file_binding.go
Normal file
159
internal/op/label_file_binding.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateLabelFileBinDingReq struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
IsDir bool `json:"is_dir"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
HashInfoStr string `json:"hashinfo"`
|
||||||
|
LabelIds string `json:"label_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjLabelResp struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
IsDir bool `json:"is_dir"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
HashInfoStr string `json:"hashinfo"`
|
||||||
|
LabelList []model.Label `json:"label_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLabelByFileName(userId uint, fileName string) ([]model.Label, error) {
|
||||||
|
labelIds, err := db.GetLabelIds(userId, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed get label_file_binding")
|
||||||
|
}
|
||||||
|
var labels []model.Label
|
||||||
|
if len(labelIds) > 0 {
|
||||||
|
if labels, err = db.GetLabelByIds(labelIds); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed labels in database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabelFileBinDing(req CreateLabelFileBinDingReq, userId uint) error {
|
||||||
|
if err := db.DelLabelFileBinDingByFileName(userId, req.Name); err != nil {
|
||||||
|
return errors.WithMessage(err, "failed del label_file_bin_ding in database")
|
||||||
|
}
|
||||||
|
if req.LabelIds == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
labelMap := strings.Split(req.LabelIds, ",")
|
||||||
|
for _, value := range labelMap {
|
||||||
|
labelId, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid label ID '%s': %v", value, err)
|
||||||
|
}
|
||||||
|
if err = db.CreateLabelFileBinDing(req.Name, uint(labelId), userId); err != nil {
|
||||||
|
return errors.WithMessage(err, "failed labels in database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !db.GetFileByNameExists(req.Name) {
|
||||||
|
objFile := model.ObjFile{
|
||||||
|
Id: req.Id,
|
||||||
|
UserId: userId,
|
||||||
|
Path: req.Path,
|
||||||
|
Name: req.Name,
|
||||||
|
Size: req.Size,
|
||||||
|
IsDir: req.IsDir,
|
||||||
|
Modified: req.Modified,
|
||||||
|
Created: req.Created,
|
||||||
|
Sign: req.Sign,
|
||||||
|
Thumb: req.Thumb,
|
||||||
|
Type: req.Type,
|
||||||
|
HashInfoStr: req.HashInfoStr,
|
||||||
|
}
|
||||||
|
err := db.CreateObjFile(objFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed file in database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileByLabel(userId uint, labelId string) (result []ObjLabelResp, err error) {
|
||||||
|
labelMap := strings.Split(labelId, ",")
|
||||||
|
var labelIds []uint
|
||||||
|
var labelsFile []model.LabelFileBinDing
|
||||||
|
var labels []model.Label
|
||||||
|
var labelsFileMap = make(map[string][]model.Label)
|
||||||
|
var labelsMap = make(map[uint]model.Label)
|
||||||
|
if labelIds, err = StringSliceToUintSlice(labelMap); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed string to uint err")
|
||||||
|
}
|
||||||
|
//查询标签信息
|
||||||
|
if labels, err = db.GetLabelByIds(labelIds); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed labels in database")
|
||||||
|
}
|
||||||
|
for _, val := range labels {
|
||||||
|
labelsMap[val.ID] = val
|
||||||
|
}
|
||||||
|
//查询标签对应文件名列表
|
||||||
|
if labelsFile, err = db.GetLabelFileBinDingByLabelId(labelIds, userId); err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed labels in database")
|
||||||
|
}
|
||||||
|
for _, value := range labelsFile {
|
||||||
|
var labelTemp model.Label
|
||||||
|
labelTemp = labelsMap[value.LabelId]
|
||||||
|
labelsFileMap[value.FileName] = append(labelsFileMap[value.FileName], labelTemp)
|
||||||
|
}
|
||||||
|
for index, v := range labelsFileMap {
|
||||||
|
objFile, err := db.GetFileByName(index, userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessage(err, "failed GetFileByName in database")
|
||||||
|
}
|
||||||
|
objLabel := ObjLabelResp{
|
||||||
|
Id: objFile.Id,
|
||||||
|
Path: objFile.Path,
|
||||||
|
Name: objFile.Name,
|
||||||
|
Size: objFile.Size,
|
||||||
|
IsDir: objFile.IsDir,
|
||||||
|
Modified: objFile.Modified,
|
||||||
|
Created: objFile.Created,
|
||||||
|
Sign: objFile.Sign,
|
||||||
|
Thumb: objFile.Thumb,
|
||||||
|
Type: objFile.Type,
|
||||||
|
HashInfoStr: objFile.HashInfoStr,
|
||||||
|
LabelList: v,
|
||||||
|
}
|
||||||
|
result = append(result, objLabel)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringSliceToUintSlice(strSlice []string) ([]uint, error) {
|
||||||
|
uintSlice := make([]uint, len(strSlice))
|
||||||
|
for i, str := range strSlice {
|
||||||
|
// 使用strconv.ParseUint将字符串转换为uint64
|
||||||
|
uint64Value, err := strconv.ParseUint(str, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // 如果转换失败,返回错误
|
||||||
|
}
|
||||||
|
// 将uint64值转换为uint(注意:这里可能存在精度损失,如果uint64值超出了uint的范围)
|
||||||
|
uintSlice[i] = uint(uint64Value)
|
||||||
|
}
|
||||||
|
return uintSlice, nil
|
||||||
|
}
|
||||||
121
internal/op/role.go
Normal file
121
internal/op/role.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Xhofe/go-cache"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/singleflight"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var roleCache = cache.NewMemCache[*model.Role](cache.WithShards[*model.Role](2))
|
||||||
|
var roleG singleflight.Group[*model.Role]
|
||||||
|
|
||||||
|
func GetRole(id uint) (*model.Role, error) {
|
||||||
|
key := fmt.Sprint(id)
|
||||||
|
if r, ok := roleCache.Get(key); ok {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, err, _ := roleG.Do(key, func() (*model.Role, error) {
|
||||||
|
_r, err := db.GetRole(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roleCache.Set(key, _r, cache.WithEx[*model.Role](time.Hour))
|
||||||
|
return _r, nil
|
||||||
|
})
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRoleByName(name string) (*model.Role, error) {
|
||||||
|
if r, ok := roleCache.Get(name); ok {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, err, _ := roleG.Do(name, func() (*model.Role, error) {
|
||||||
|
_r, err := db.GetRoleByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roleCache.Set(name, _r, cache.WithEx[*model.Role](time.Hour))
|
||||||
|
return _r, nil
|
||||||
|
})
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRolesByUserID(userID uint) ([]model.Role, error) {
|
||||||
|
user, err := GetUserById(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []model.Role
|
||||||
|
for _, roleID := range user.Role {
|
||||||
|
key := fmt.Sprint(roleID)
|
||||||
|
|
||||||
|
if r, ok := roleCache.Get(key); ok {
|
||||||
|
roles = append(roles, *r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err, _ := roleG.Do(key, func() (*model.Role, error) {
|
||||||
|
_r, err := db.GetRole(uint(roleID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roleCache.Set(key, _r, cache.WithEx[*model.Role](time.Hour))
|
||||||
|
return _r, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
roles = append(roles, *r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRoles(pageIndex, pageSize int) ([]model.Role, int64, error) {
|
||||||
|
return db.GetRoles(pageIndex, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRole(r *model.Role) error {
|
||||||
|
for i := range r.PermissionScopes {
|
||||||
|
r.PermissionScopes[i].Path = utils.FixAndCleanPath(r.PermissionScopes[i].Path)
|
||||||
|
}
|
||||||
|
roleCache.Del(fmt.Sprint(r.ID))
|
||||||
|
roleCache.Del(r.Name)
|
||||||
|
return db.CreateRole(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRole(r *model.Role) error {
|
||||||
|
old, err := db.GetRole(r.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if old.Name == "admin" || old.Name == "guest" {
|
||||||
|
return errs.ErrChangeDefaultRole
|
||||||
|
}
|
||||||
|
for i := range r.PermissionScopes {
|
||||||
|
r.PermissionScopes[i].Path = utils.FixAndCleanPath(r.PermissionScopes[i].Path)
|
||||||
|
}
|
||||||
|
roleCache.Del(fmt.Sprint(r.ID))
|
||||||
|
roleCache.Del(r.Name)
|
||||||
|
return db.UpdateRole(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRole(id uint) error {
|
||||||
|
old, err := db.GetRole(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if old.Name == "admin" || old.Name == "guest" {
|
||||||
|
return errs.ErrChangeDefaultRole
|
||||||
|
}
|
||||||
|
roleCache.Del(fmt.Sprint(id))
|
||||||
|
roleCache.Del(old.Name)
|
||||||
|
return db.DeleteRole(id)
|
||||||
|
}
|
||||||
@@ -216,6 +216,21 @@ func UpdateStorage(ctx context.Context, storage model.Storage) error {
|
|||||||
if oldStorage.MountPath != storage.MountPath {
|
if oldStorage.MountPath != storage.MountPath {
|
||||||
// mount path renamed, need to drop the storage
|
// mount path renamed, need to drop the storage
|
||||||
storagesMap.Delete(oldStorage.MountPath)
|
storagesMap.Delete(oldStorage.MountPath)
|
||||||
|
modifiedRoleIDs, err := db.UpdateRolePermissionsPathPrefix(oldStorage.MountPath, storage.MountPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed to update role permissions")
|
||||||
|
}
|
||||||
|
for _, id := range modifiedRoleIDs {
|
||||||
|
roleCache.Del(fmt.Sprint(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedUsernames, err := db.UpdateUserBasePathPrefix(oldStorage.MountPath, storage.MountPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed to update user base path")
|
||||||
|
}
|
||||||
|
for _, name := range modifiedUsernames {
|
||||||
|
userCache.Del(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithMessage(err, "failed get storage driver")
|
return errors.WithMessage(err, "failed get storage driver")
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ var adminUser *model.User
|
|||||||
|
|
||||||
func GetAdmin() (*model.User, error) {
|
func GetAdmin() (*model.User, error) {
|
||||||
if adminUser == nil {
|
if adminUser == nil {
|
||||||
user, err := db.GetUserByRole(model.ADMIN)
|
role, err := GetRoleByName("admin")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user, err := db.GetUserByRole(int(role.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -29,7 +33,11 @@ func GetAdmin() (*model.User, error) {
|
|||||||
|
|
||||||
func GetGuest() (*model.User, error) {
|
func GetGuest() (*model.User, error) {
|
||||||
if guestUser == nil {
|
if guestUser == nil {
|
||||||
user, err := db.GetUserByRole(model.GUEST)
|
role, err := GetRoleByName("guest")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user, err := db.GetUserByRole(int(role.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/dlclark/regexp2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsStorageSignEnabled(rawPath string) bool {
|
func IsStorageSignEnabled(rawPath string) bool {
|
||||||
@@ -32,30 +28,11 @@ func IsApply(metaPath, reqPath string, applySub bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
||||||
// if the reqPath is in hide (only can check the nearest meta) and user can't see hides, can't access
|
// Deprecated: CanAccess is kept for backward compatibility.
|
||||||
if meta != nil && !user.CanSeeHides() && meta.Hide != "" &&
|
// The logic has been moved to CanAccessWithRoles which performs the
|
||||||
IsApply(meta.Path, path.Dir(reqPath), meta.HSub) { // the meta should apply to the parent of current path
|
// necessary checks based on role permissions. This wrapper ensures
|
||||||
for _, hide := range strings.Split(meta.Hide, "\n") {
|
// older calls still work without relying on user permission bits.
|
||||||
re := regexp2.MustCompile(hide, regexp2.None)
|
return CanAccessWithRoles(user, meta, reqPath, password)
|
||||||
if isMatch, _ := re.MatchString(path.Base(reqPath)); isMatch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if is not guest and can access without password
|
|
||||||
if user.CanAccessWithoutPassword() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// if meta is nil or password is empty, can access
|
|
||||||
if meta == nil || meta.Password == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// if meta doesn't apply to sub_folder, can access
|
|
||||||
if !utils.PathEqual(meta.Path, reqPath) && !meta.PSub {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// validate password
|
|
||||||
return meta.Password == password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldProxy TODO need optimize
|
// ShouldProxy TODO need optimize
|
||||||
|
|||||||
108
server/common/role_perm.go
Normal file
108
server/common/role_perm.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PermSeeHides = iota
|
||||||
|
PermAccessWithoutPassword
|
||||||
|
PermAddOfflineDownload
|
||||||
|
PermWrite
|
||||||
|
PermRename
|
||||||
|
PermMove
|
||||||
|
PermCopy
|
||||||
|
PermRemove
|
||||||
|
PermWebdavRead
|
||||||
|
PermWebdavManage
|
||||||
|
PermFTPAccess
|
||||||
|
PermFTPManage
|
||||||
|
PermReadArchives
|
||||||
|
PermDecompress
|
||||||
|
PermPathLimit
|
||||||
|
)
|
||||||
|
|
||||||
|
func HasPermission(perm int32, bit uint) bool {
|
||||||
|
return (perm>>bit)&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeRolePermissions(u *model.User, reqPath string) int32 {
|
||||||
|
if u == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var perm int32
|
||||||
|
for _, rid := range u.Role {
|
||||||
|
role, err := op.GetRole(uint(rid))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, entry := range role.PermissionScopes {
|
||||||
|
if utils.IsSubPath(entry.Path, reqPath) {
|
||||||
|
perm |= entry.Permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return perm
|
||||||
|
}
|
||||||
|
|
||||||
|
func CanAccessWithRoles(u *model.User, meta *model.Meta, reqPath, password string) bool {
|
||||||
|
if !canReadPathByRole(u, reqPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
perm := MergeRolePermissions(u, reqPath)
|
||||||
|
if meta != nil && !HasPermission(perm, PermSeeHides) && meta.Hide != "" &&
|
||||||
|
IsApply(meta.Path, path.Dir(reqPath), meta.HSub) {
|
||||||
|
for _, hide := range strings.Split(meta.Hide, "\n") {
|
||||||
|
re := regexp2.MustCompile(hide, regexp2.None)
|
||||||
|
if isMatch, _ := re.MatchString(path.Base(reqPath)); isMatch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if HasPermission(perm, PermAccessWithoutPassword) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if meta == nil || meta.Password == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !utils.PathEqual(meta.Path, reqPath) && !meta.PSub {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return meta.Password == password
|
||||||
|
}
|
||||||
|
|
||||||
|
func canReadPathByRole(u *model.User, reqPath string) bool {
|
||||||
|
if u == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, rid := range u.Role {
|
||||||
|
role, err := op.GetRole(uint(rid))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, entry := range role.PermissionScopes {
|
||||||
|
if utils.IsSubPath(entry.Path, reqPath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPathLimitWithRoles checks whether the path is allowed when the user has
|
||||||
|
// the `PermPathLimit` permission for the target path. When the user does not
|
||||||
|
// have this permission, the check passes by default.
|
||||||
|
func CheckPathLimitWithRoles(u *model.User, reqPath string) bool {
|
||||||
|
perm := MergeRolePermissions(u, reqPath)
|
||||||
|
if HasPermission(perm, PermPathLimit) {
|
||||||
|
return canReadPathByRole(u, reqPath)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/alist-org/alist/v3/server/ftp"
|
"github.com/alist-org/alist/v3/server/ftp"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@@ -130,7 +131,8 @@ func (d *FtpMainDriver) AuthUser(cc ftpserver.ClientContext, user, pass string)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if userObj.Disabled || !userObj.CanFTPAccess() {
|
perm := common.MergeRolePermissions(userObj, userObj.BasePath)
|
||||||
|
if userObj.Disabled || !common.HasPermission(perm, common.PermFTPAccess) {
|
||||||
return nil, errors.New("user is not allowed to access via FTP")
|
return nil, errors.New("user is not allowed to access via FTP")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ func Mkdir(ctx context.Context, path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !user.CanWrite() || !user.CanFTPManage() {
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermWrite) || !common.HasPermission(perm, common.PermFTPManage) {
|
||||||
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
|
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
@@ -34,7 +35,8 @@ func Mkdir(ctx context.Context, path string) error {
|
|||||||
|
|
||||||
func Remove(ctx context.Context, path string) error {
|
func Remove(ctx context.Context, path string) error {
|
||||||
user := ctx.Value("user").(*model.User)
|
user := ctx.Value("user").(*model.User)
|
||||||
if !user.CanRemove() || !user.CanFTPManage() {
|
perm := common.MergeRolePermissions(user, path)
|
||||||
|
if !common.HasPermission(perm, common.PermRemove) || !common.HasPermission(perm, common.PermFTPManage) {
|
||||||
return errs.PermissionDenied
|
return errs.PermissionDenied
|
||||||
}
|
}
|
||||||
reqPath, err := user.JoinPath(path)
|
reqPath, err := user.JoinPath(path)
|
||||||
@@ -56,13 +58,14 @@ func Rename(ctx context.Context, oldPath, newPath string) error {
|
|||||||
}
|
}
|
||||||
srcDir, srcBase := stdpath.Split(srcPath)
|
srcDir, srcBase := stdpath.Split(srcPath)
|
||||||
dstDir, dstBase := stdpath.Split(dstPath)
|
dstDir, dstBase := stdpath.Split(dstPath)
|
||||||
|
permSrc := common.MergeRolePermissions(user, srcPath)
|
||||||
if srcDir == dstDir {
|
if srcDir == dstDir {
|
||||||
if !user.CanRename() || !user.CanFTPManage() {
|
if !common.HasPermission(permSrc, common.PermRename) || !common.HasPermission(permSrc, common.PermFTPManage) {
|
||||||
return errs.PermissionDenied
|
return errs.PermissionDenied
|
||||||
}
|
}
|
||||||
return fs.Rename(ctx, srcPath, dstBase)
|
return fs.Rename(ctx, srcPath, dstBase)
|
||||||
} else {
|
} else {
|
||||||
if !user.CanFTPManage() || !user.CanMove() || (srcBase != dstBase && !user.CanRename()) {
|
if !common.HasPermission(permSrc, common.PermFTPManage) || !common.HasPermission(permSrc, common.PermMove) || (srcBase != dstBase && !common.HasPermission(permSrc, common.PermRename)) {
|
||||||
return errs.PermissionDenied
|
return errs.PermissionDenied
|
||||||
}
|
}
|
||||||
if err = fs.Move(ctx, srcPath, dstDir); err != nil {
|
if err = fs.Move(ctx, srcPath, dstDir); err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func OpenDownload(ctx context.Context, reqPath string, offset int64) (*FileDownl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, "meta", meta)
|
ctx = context.WithValue(ctx, "meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
||||||
return nil, errs.PermissionDenied
|
return nil, errs.PermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ func Stat(ctx context.Context, path string) (os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, "meta", meta)
|
ctx = context.WithValue(ctx, "meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
||||||
return nil, errs.PermissionDenied
|
return nil, errs.PermissionDenied
|
||||||
}
|
}
|
||||||
obj, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
|
obj, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
|
||||||
@@ -148,7 +148,7 @@ func List(ctx context.Context, path string) ([]os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, "meta", meta)
|
ctx = context.WithValue(ctx, "meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, ctx.Value("meta_pass").(string)) {
|
||||||
return nil, errs.PermissionDenied
|
return nil, errs.PermissionDenied
|
||||||
}
|
}
|
||||||
objs, err := fs.List(ctx, reqPath, &fs.ListArgs{})
|
objs, err := fs.List(ctx, reqPath, &fs.ListArgs{})
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ func uploadAuth(ctx context.Context, path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !(common.CanAccess(user, meta, path, ctx.Value("meta_pass").(string)) &&
|
perm := common.MergeRolePermissions(user, path)
|
||||||
((user.CanFTPManage() && user.CanWrite()) || common.CanWrite(meta, stdpath.Dir(path)))) {
|
if !(common.CanAccessWithRoles(user, meta, path, ctx.Value("meta_pass").(string)) &&
|
||||||
|
((common.HasPermission(perm, common.PermFTPManage) && common.HasPermission(perm, common.PermWrite)) ||
|
||||||
|
common.CanWrite(meta, stdpath.Dir(path)))) {
|
||||||
return errs.PermissionDenied
|
return errs.PermissionDenied
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -78,15 +78,20 @@ func FsArchiveMeta(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanReadArchives() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reqPath, err := user.JoinPath(req.Path)
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermReadArchives) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
meta, err := op.GetNearestMeta(reqPath)
|
meta, err := op.GetNearestMeta(reqPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
@@ -156,15 +161,20 @@ func FsArchiveList(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
req.Validate()
|
req.Validate()
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanReadArchives() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reqPath, err := user.JoinPath(req.Path)
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermReadArchives) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
meta, err := op.GetNearestMeta(reqPath)
|
meta, err := op.GetNearestMeta(reqPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
@@ -242,10 +252,6 @@ func FsArchiveDecompress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanDecompress() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcPaths := make([]string, 0, len(req.Name))
|
srcPaths := make([]string, 0, len(req.Name))
|
||||||
for _, name := range req.Name {
|
for _, name := range req.Name {
|
||||||
srcPath, err := user.JoinPath(stdpath.Join(req.SrcDir, name))
|
srcPath, err := user.JoinPath(stdpath.Join(req.SrcDir, name))
|
||||||
@@ -253,6 +259,10 @@ func FsArchiveDecompress(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, srcPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
srcPaths = append(srcPaths, srcPath)
|
srcPaths = append(srcPaths, srcPath)
|
||||||
}
|
}
|
||||||
dstDir, err := user.JoinPath(req.DstDir)
|
dstDir, err := user.JoinPath(req.DstDir)
|
||||||
@@ -260,8 +270,17 @@ func FsArchiveDecompress(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, dstDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
tasks := make([]task.TaskExtensionInfo, 0, len(srcPaths))
|
tasks := make([]task.TaskExtensionInfo, 0, len(srcPaths))
|
||||||
for _, srcPath := range srcPaths {
|
for _, srcPath := range srcPaths {
|
||||||
|
perm := common.MergeRolePermissions(user, srcPath)
|
||||||
|
if !common.HasPermission(perm, common.PermDecompress) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
t, e := fs.ArchiveDecompress(c, srcPath, dstDir, model.ArchiveDecompressArgs{
|
t, e := fs.ArchiveDecompress(c, srcPath, dstDir, model.ArchiveDecompressArgs{
|
||||||
ArchiveInnerArgs: model.ArchiveInnerArgs{
|
ArchiveInnerArgs: model.ArchiveInnerArgs{
|
||||||
ArchiveArgs: model.ArchiveArgs{
|
ArchiveArgs: model.ArchiveArgs{
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Xhofe/go-cache"
|
"github.com/Xhofe/go-cache"
|
||||||
@@ -89,13 +91,16 @@ func loginHash(c *gin.Context, req *LoginReq) {
|
|||||||
|
|
||||||
type UserResp struct {
|
type UserResp struct {
|
||||||
model.User
|
model.User
|
||||||
Otp bool `json:"otp"`
|
Otp bool `json:"otp"`
|
||||||
|
RoleNames []string `json:"role_names"`
|
||||||
|
Permissions []model.PermissionEntry `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentUser get current user by token
|
// CurrentUser get current user by token
|
||||||
// if token is empty, return guest user
|
// if token is empty, return guest user
|
||||||
func CurrentUser(c *gin.Context) {
|
func CurrentUser(c *gin.Context) {
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
|
|
||||||
userResp := UserResp{
|
userResp := UserResp{
|
||||||
User: *user,
|
User: *user,
|
||||||
}
|
}
|
||||||
@@ -103,6 +108,30 @@ func CurrentUser(c *gin.Context) {
|
|||||||
if userResp.OtpSecret != "" {
|
if userResp.OtpSecret != "" {
|
||||||
userResp.Otp = true
|
userResp.Otp = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var roleNames []string
|
||||||
|
permMap := map[string]int32{}
|
||||||
|
addedPaths := map[string]bool{}
|
||||||
|
|
||||||
|
for _, role := range user.RolesDetail {
|
||||||
|
roleNames = append(roleNames, role.Name)
|
||||||
|
for _, entry := range role.PermissionScopes {
|
||||||
|
cleanPath := path.Clean("/" + strings.TrimPrefix(entry.Path, "/"))
|
||||||
|
permMap[cleanPath] |= entry.Permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userResp.RoleNames = roleNames
|
||||||
|
|
||||||
|
for fullPath, perm := range permMap {
|
||||||
|
if !addedPaths[fullPath] {
|
||||||
|
userResp.Permissions = append(userResp.Permissions, model.PermissionEntry{
|
||||||
|
Path: fullPath,
|
||||||
|
Permission: perm,
|
||||||
|
})
|
||||||
|
addedPaths[fullPath] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
common.SuccessResp(c, userResp)
|
common.SuccessResp(c, userResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,20 +29,29 @@ func FsRecursiveMove(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanMove() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcDir, err := user.JoinPath(req.SrcDir)
|
srcDir, err := user.JoinPath(req.SrcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, srcDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
dstDir, err := user.JoinPath(req.DstDir)
|
dstDir, err := user.JoinPath(req.DstDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, dstDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, srcDir)
|
||||||
|
if !common.HasPermission(perm, common.PermMove) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
meta, err := op.GetNearestMeta(srcDir)
|
meta, err := op.GetNearestMeta(srcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -149,16 +158,20 @@ func FsBatchRename(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanRename() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqPath, err := user.JoinPath(req.SrcDir)
|
reqPath, err := user.JoinPath(req.SrcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermRename) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
meta, err := op.GetNearestMeta(reqPath)
|
meta, err := op.GetNearestMeta(reqPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -194,14 +207,19 @@ func FsRegexRename(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanRename() {
|
reqPath, err := user.JoinPath(req.SrcDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reqPath, err := user.JoinPath(req.SrcDir)
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
if err != nil {
|
if !common.HasPermission(perm, common.PermRename) {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ func FsMkdir(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !user.CanWrite() {
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermWrite) {
|
||||||
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
|
meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
@@ -73,20 +78,29 @@ func FsMove(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanMove() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcDir, err := user.JoinPath(req.SrcDir)
|
srcDir, err := user.JoinPath(req.SrcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, srcDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
dstDir, err := user.JoinPath(req.DstDir)
|
dstDir, err := user.JoinPath(req.DstDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, dstDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
permMove := common.MergeRolePermissions(user, srcDir)
|
||||||
|
if !common.HasPermission(permMove, common.PermMove) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !req.Overwrite {
|
if !req.Overwrite {
|
||||||
for _, name := range req.Names {
|
for _, name := range req.Names {
|
||||||
if res, _ := fs.Get(c, stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
if res, _ := fs.Get(c, stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
||||||
@@ -116,20 +130,29 @@ func FsCopy(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanCopy() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcDir, err := user.JoinPath(req.SrcDir)
|
srcDir, err := user.JoinPath(req.SrcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, srcDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
dstDir, err := user.JoinPath(req.DstDir)
|
dstDir, err := user.JoinPath(req.DstDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, dstDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, srcDir)
|
||||||
|
if !common.HasPermission(perm, common.PermCopy) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !req.Overwrite {
|
if !req.Overwrite {
|
||||||
for _, name := range req.Names {
|
for _, name := range req.Names {
|
||||||
if res, _ := fs.Get(c, stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
if res, _ := fs.Get(c, stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
||||||
@@ -167,15 +190,20 @@ func FsRename(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanRename() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reqPath, err := user.JoinPath(req.Path)
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermRename) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !req.Overwrite {
|
if !req.Overwrite {
|
||||||
dstPath := stdpath.Join(stdpath.Dir(reqPath), req.Name)
|
dstPath := stdpath.Join(stdpath.Dir(reqPath), req.Name)
|
||||||
if dstPath != reqPath {
|
if dstPath != reqPath {
|
||||||
@@ -208,15 +236,20 @@ func FsRemove(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanRemove() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reqDir, err := user.JoinPath(req.Dir)
|
reqDir, err := user.JoinPath(req.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqDir)
|
||||||
|
if !common.HasPermission(perm, common.PermRemove) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, name := range req.Names {
|
for _, name := range req.Names {
|
||||||
err := fs.Remove(c, stdpath.Join(reqDir, name))
|
err := fs.Remove(c, stdpath.Join(reqDir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,15 +273,20 @@ func FsRemoveEmptyDirectory(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanRemove() {
|
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
srcDir, err := user.JoinPath(req.SrcDir)
|
srcDir, err := user.JoinPath(req.SrcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, srcDir) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, srcDir)
|
||||||
|
if !common.HasPermission(perm, common.PermRemove) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
meta, err := op.GetNearestMeta(srcDir)
|
meta, err := op.GetNearestMeta(srcDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -48,12 +48,28 @@ type ObjResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FsListResp struct {
|
type FsListResp struct {
|
||||||
Content []ObjResp `json:"content"`
|
Content []ObjLabelResp `json:"content"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Readme string `json:"readme"`
|
Readme string `json:"readme"`
|
||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
Write bool `json:"write"`
|
Write bool `json:"write"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjLabelResp struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
IsDir bool `json:"is_dir"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
HashInfoStr string `json:"hashinfo"`
|
||||||
|
HashInfo map[*utils.HashType]string `json:"hash_info"`
|
||||||
|
LabelList []model.Label `json:"label_list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FsList(c *gin.Context) {
|
func FsList(c *gin.Context) {
|
||||||
@@ -77,11 +93,12 @@ func FsList(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Set("meta", meta)
|
c.Set("meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !user.CanWrite() && !common.CanWrite(meta, reqPath) && req.Refresh {
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermWrite) && !common.CanWrite(meta, reqPath) && req.Refresh {
|
||||||
common.ErrorStrResp(c, "Refresh without permission", 403)
|
common.ErrorStrResp(c, "Refresh without permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,11 +114,11 @@ func FsList(c *gin.Context) {
|
|||||||
provider = storage.GetStorage().Driver
|
provider = storage.GetStorage().Driver
|
||||||
}
|
}
|
||||||
common.SuccessResp(c, FsListResp{
|
common.SuccessResp(c, FsListResp{
|
||||||
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath)),
|
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath), user.ID),
|
||||||
Total: int64(total),
|
Total: int64(total),
|
||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
Header: getHeader(meta, reqPath),
|
Header: getHeader(meta, reqPath),
|
||||||
Write: user.CanWrite() || common.CanWrite(meta, reqPath),
|
Write: common.HasPermission(perm, common.PermWrite) || common.CanWrite(meta, reqPath),
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -135,7 +152,7 @@ func FsDirs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Set("meta", meta)
|
c.Set("meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -207,11 +224,15 @@ func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
|
|||||||
return total, objs[start:end]
|
return total, objs[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
func toObjsResp(objs []model.Obj, parent string, encrypt bool, userId uint) []ObjLabelResp {
|
||||||
var resp []ObjResp
|
var resp []ObjLabelResp
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
|
var labels []model.Label
|
||||||
|
if obj.IsDir() == false {
|
||||||
|
labels, _ = op.GetLabelByFileName(userId, obj.GetName())
|
||||||
|
}
|
||||||
thumb, _ := model.GetThumb(obj)
|
thumb, _ := model.GetThumb(obj)
|
||||||
resp = append(resp, ObjResp{
|
resp = append(resp, ObjLabelResp{
|
||||||
Id: obj.GetID(),
|
Id: obj.GetID(),
|
||||||
Path: obj.GetPath(),
|
Path: obj.GetPath(),
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
@@ -224,6 +245,7 @@ func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
|||||||
Sign: common.Sign(obj, parent, encrypt),
|
Sign: common.Sign(obj, parent, encrypt),
|
||||||
Thumb: thumb,
|
Thumb: thumb,
|
||||||
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||||
|
LabelList: labels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
@@ -236,11 +258,11 @@ type FsGetReq struct {
|
|||||||
|
|
||||||
type FsGetResp struct {
|
type FsGetResp struct {
|
||||||
ObjResp
|
ObjResp
|
||||||
RawURL string `json:"raw_url"`
|
RawURL string `json:"raw_url"`
|
||||||
Readme string `json:"readme"`
|
Readme string `json:"readme"`
|
||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Related []ObjResp `json:"related"`
|
Related []ObjLabelResp `json:"related"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FsGet(c *gin.Context) {
|
func FsGet(c *gin.Context) {
|
||||||
@@ -263,7 +285,7 @@ func FsGet(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Set("meta", meta)
|
c.Set("meta", meta)
|
||||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
if !common.CanAccessWithRoles(user, meta, reqPath, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -347,7 +369,7 @@ func FsGet(c *gin.Context) {
|
|||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
Header: getHeader(meta, reqPath),
|
Header: getHeader(meta, reqPath),
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
|
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath), user.ID),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +413,7 @@ func FsOther(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Set("meta", meta)
|
c.Set("meta", meta)
|
||||||
if !common.CanAccess(user, meta, req.Path, req.Password) {
|
if !common.CanAccessWithRoles(user, meta, req.Path, req.Password) {
|
||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
99
server/handles/label.go
Normal file
99
server/handles/label.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListLabel(c *gin.Context) {
|
||||||
|
var req model.PageReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Validate()
|
||||||
|
log.Debugf("%+v", req)
|
||||||
|
labels, total, err := db.GetLabels(req.Page, req.PerPage)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, common.PageResp{
|
||||||
|
Content: labels,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLabel(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
label, err := db.GetLabelById(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabel(c *gin.Context) {
|
||||||
|
var req model.Label
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if db.GetLabelByName(req.Name) {
|
||||||
|
common.ErrorResp(c, errors.New("label name is exists"), 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if id, err := db.CreateLabel(req); err != nil {
|
||||||
|
common.ErrorWithDataResp(c, err, 500, gin.H{
|
||||||
|
"id": id,
|
||||||
|
}, true)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c, gin.H{
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateLabel(c *gin.Context) {
|
||||||
|
var req model.Label
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if label, err := db.UpdateLabel(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteLabel(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = op.DeleteLabelById(c, uint(id), userObj.ID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
103
server/handles/label_file_binding.go
Normal file
103
server/handles/label_file_binding.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DelLabelFileBinDingReq struct {
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
LabelId string `json:"label_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLabelByFileName(c *gin.Context) {
|
||||||
|
fileName := c.Query("file_name")
|
||||||
|
if fileName == "" {
|
||||||
|
common.ErrorResp(c, errors.New("file_name must not empty"), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labels, err := op.GetLabelByFileName(userObj.ID, fileName)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabelFileBinDing(c *gin.Context) {
|
||||||
|
var req op.CreateLabelFileBinDingReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.IsDir == true {
|
||||||
|
common.ErrorStrResp(c, "Unable to bind folder", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := op.CreateLabelFileBinDing(req, userObj.ID); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c, gin.H{
|
||||||
|
"msg": "添加成功!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelLabelByFileName(c *gin.Context) {
|
||||||
|
var req DelLabelFileBinDingReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labelId, err := strconv.ParseUint(req.LabelId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, fmt.Errorf("invalid label ID '%s': %v", req.LabelId, err), 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = db.DelLabelFileBinDingById(uint(labelId), userObj.ID, req.FileName); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileByLabel(c *gin.Context) {
|
||||||
|
labelId := c.Query("label_id")
|
||||||
|
if labelId == "" {
|
||||||
|
common.ErrorResp(c, errors.New("file_name must not empty"), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileList, err := op.GetFileByLabel(userObj.ID, labelId)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, fileList)
|
||||||
|
}
|
||||||
@@ -131,7 +131,7 @@ func ladpRegister(username string) (*model.User, error) {
|
|||||||
Password: random.String(16),
|
Password: random.String(16),
|
||||||
Permission: int32(setting.GetInt(conf.LdapDefaultPermission, 0)),
|
Permission: int32(setting.GetInt(conf.LdapDefaultPermission, 0)),
|
||||||
BasePath: setting.GetStr(conf.LdapDefaultDir),
|
BasePath: setting.GetStr(conf.LdapDefaultDir),
|
||||||
Role: 0,
|
Role: nil,
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}
|
}
|
||||||
if err := db.CreateUser(user); err != nil {
|
if err := db.CreateUser(user); err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/drivers/pikpak"
|
"github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
"github.com/alist-org/alist/v3/drivers/thunder"
|
"github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
@@ -253,10 +254,6 @@ type AddOfflineDownloadReq struct {
|
|||||||
|
|
||||||
func AddOfflineDownload(c *gin.Context) {
|
func AddOfflineDownload(c *gin.Context) {
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.CanAddOfflineDownloadTasks() {
|
|
||||||
common.ErrorStrResp(c, "permission denied", 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var req AddOfflineDownloadReq
|
var req AddOfflineDownloadReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
@@ -268,6 +265,15 @@ func AddOfflineDownload(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !common.CheckPathLimitWithRoles(user, reqPath) {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if !common.HasPermission(perm, common.PermAddOfflineDownload) {
|
||||||
|
common.ErrorStrResp(c, "permission denied", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
var tasks []task.TaskExtensionInfo
|
var tasks []task.TaskExtensionInfo
|
||||||
for _, url := range req.Urls {
|
for _, url := range req.Urls {
|
||||||
t, err := tool.AddURL(c, &tool.AddURLArgs{
|
t, err := tool.AddURL(c, &tool.AddURLArgs{
|
||||||
|
|||||||
101
server/handles/role.go
Normal file
101
server/handles/role.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListRoles(c *gin.Context) {
|
||||||
|
var req model.PageReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Validate()
|
||||||
|
log.Debugf("%+v", req)
|
||||||
|
roles, total, err := op.GetRoles(req.Page, req.PerPage)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, common.PageResp{Content: roles, Total: total})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRole(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
role, err := op.GetRole(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRole(c *gin.Context) {
|
||||||
|
var req model.Role
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := op.CreateRole(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRole(c *gin.Context) {
|
||||||
|
var req model.Role
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
role, err := op.GetRole(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if role.Name == "admin" || role.Name == "guest" {
|
||||||
|
common.ErrorResp(c, errs.ErrChangeDefaultRole, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := op.UpdateRole(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRole(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
role, err := op.GetRole(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if role.Name == "admin" || role.Name == "guest" {
|
||||||
|
common.ErrorResp(c, errs.ErrChangeDefaultRole, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := op.DeleteRole(uint(id)); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ func Search(c *gin.Context) {
|
|||||||
if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !common.CanAccess(user, meta, path.Join(node.Parent, node.Name), req.Password) {
|
if !common.CanAccessWithRoles(user, meta, path.Join(node.Parent, node.Name), req.Password) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filteredNodes = append(filteredNodes, node)
|
filteredNodes = append(filteredNodes, node)
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ func autoRegister(username, userID string, err error) (*model.User, error) {
|
|||||||
Password: random.String(16),
|
Password: random.String(16),
|
||||||
Permission: int32(setting.GetInt(conf.SSODefaultPermission, 0)),
|
Permission: int32(setting.GetInt(conf.SSODefaultPermission, 0)),
|
||||||
BasePath: setting.GetStr(conf.SSODefaultDir),
|
BasePath: setting.GetStr(conf.SSODefaultDir),
|
||||||
Role: 0,
|
Role: nil,
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
SsoID: userID,
|
SsoID: userID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type TaskInfo struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Creator string `json:"creator"`
|
Creator string `json:"creator"`
|
||||||
CreatorRole int `json:"creator_role"`
|
CreatorRole model.Roles `json:"creator_role"`
|
||||||
State tache.State `json:"state"`
|
State tache.State `json:"state"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Progress float64 `json:"progress"`
|
Progress float64 `json:"progress"`
|
||||||
@@ -39,7 +39,7 @@ func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo {
|
|||||||
progress = 100
|
progress = 100
|
||||||
}
|
}
|
||||||
creatorName := ""
|
creatorName := ""
|
||||||
creatorRole := -1
|
var creatorRole model.Roles
|
||||||
if task.GetCreator() != nil {
|
if task.GetCreator() != nil {
|
||||||
creatorName = task.GetCreator().Username
|
creatorName = task.GetCreator().Username
|
||||||
creatorRole = task.GetCreator().Role
|
creatorRole = task.GetCreator().Role
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ func UpdateUser(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Role != req.Role {
|
//if !utils.SliceEqual(user.Role, req.Role) {
|
||||||
common.ErrorStrResp(c, "role can not be changed", 400)
|
// common.ErrorStrResp(c, "role can not be changed", 400)
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
if req.Password == "" {
|
if req.Password == "" {
|
||||||
req.PwdHash = user.PwdHash
|
req.PwdHash = user.PwdHash
|
||||||
req.Salt = user.Salt
|
req.Salt = user.Salt
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package middlewares
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
@@ -68,6 +69,15 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(user.Role) > 0 {
|
||||||
|
roles, err := op.GetRolesByUserID(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("Fail to load roles: %v", err), 500)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.RolesDetail = roles
|
||||||
|
}
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
log.Debugf("use login token: %+v", user)
|
log.Debugf("use login token: %+v", user)
|
||||||
c.Next()
|
c.Next()
|
||||||
@@ -122,6 +132,19 @@ func Authn(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(user.Role) > 0 {
|
||||||
|
var roles []model.Role
|
||||||
|
for _, roleID := range user.Role {
|
||||||
|
role, err := op.GetRole(uint(roleID))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("load role %d failed", roleID), 500)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roles = append(roles, *role)
|
||||||
|
}
|
||||||
|
user.RolesDetail = roles
|
||||||
|
}
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
log.Debugf("use login token: %+v", user)
|
log.Debugf("use login token: %+v", user)
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ func FsUp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, stdpath.Dir(path)))) {
|
perm := common.MergeRolePermissions(user, path)
|
||||||
|
if !(common.CanAccessWithRoles(user, meta, path, password) &&
|
||||||
|
(common.HasPermission(perm, common.PermWrite) || common.CanWrite(meta, stdpath.Dir(path)))) {
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -120,6 +120,13 @@ func admin(g *gin.RouterGroup) {
|
|||||||
user.GET("/sshkey/list", handles.ListPublicKeys)
|
user.GET("/sshkey/list", handles.ListPublicKeys)
|
||||||
user.POST("/sshkey/delete", handles.DeletePublicKey)
|
user.POST("/sshkey/delete", handles.DeletePublicKey)
|
||||||
|
|
||||||
|
role := g.Group("/role")
|
||||||
|
role.GET("/list", handles.ListRoles)
|
||||||
|
role.GET("/get", handles.GetRole)
|
||||||
|
role.POST("/create", handles.CreateRole)
|
||||||
|
role.POST("/update", handles.UpdateRole)
|
||||||
|
role.POST("/delete", handles.DeleteRole)
|
||||||
|
|
||||||
storage := g.Group("/storage")
|
storage := g.Group("/storage")
|
||||||
storage.GET("/list", handles.ListStorages)
|
storage.GET("/list", handles.ListStorages)
|
||||||
storage.GET("/get", handles.GetStorage)
|
storage.GET("/get", handles.GetStorage)
|
||||||
@@ -161,6 +168,19 @@ func admin(g *gin.RouterGroup) {
|
|||||||
index.POST("/stop", middlewares.SearchIndex, handles.StopIndex)
|
index.POST("/stop", middlewares.SearchIndex, handles.StopIndex)
|
||||||
index.POST("/clear", middlewares.SearchIndex, handles.ClearIndex)
|
index.POST("/clear", middlewares.SearchIndex, handles.ClearIndex)
|
||||||
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
||||||
|
|
||||||
|
label := g.Group("/label")
|
||||||
|
label.GET("/list", handles.ListLabel)
|
||||||
|
label.GET("/get", handles.GetLabel)
|
||||||
|
label.POST("/create", handles.CreateLabel)
|
||||||
|
label.POST("/update", handles.UpdateLabel)
|
||||||
|
label.POST("/delete", handles.DeleteLabel)
|
||||||
|
|
||||||
|
labelFileBinding := g.Group("/label_file_binding")
|
||||||
|
labelFileBinding.GET("/get", handles.GetLabelByFileName)
|
||||||
|
labelFileBinding.GET("/get_file_by_label", handles.GetFileByLabel)
|
||||||
|
labelFileBinding.POST("/create", handles.CreateLabelFileBinDing)
|
||||||
|
labelFileBinding.POST("/delete", handles.DelLabelByFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _fs(g *gin.RouterGroup) {
|
func _fs(g *gin.RouterGroup) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/alist-org/alist/v3/server/ftp"
|
"github.com/alist-org/alist/v3/server/ftp"
|
||||||
"github.com/alist-org/alist/v3/server/sftp"
|
"github.com/alist-org/alist/v3/server/sftp"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -78,7 +79,8 @@ func (d *SftpDriver) NoClientAuth(conn ssh.ConnMetadata) (*ssh.Permissions, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if guest.Disabled || !guest.CanFTPAccess() {
|
permGuest := common.MergeRolePermissions(guest, guest.BasePath)
|
||||||
|
if guest.Disabled || !common.HasPermission(permGuest, common.PermFTPAccess) {
|
||||||
return nil, errors.New("user is not allowed to access via SFTP")
|
return nil, errors.New("user is not allowed to access via SFTP")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -89,7 +91,8 @@ func (d *SftpDriver) PasswordAuth(conn ssh.ConnMetadata, password []byte) (*ssh.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if userObj.Disabled || !userObj.CanFTPAccess() {
|
perm := common.MergeRolePermissions(userObj, userObj.BasePath)
|
||||||
|
if userObj.Disabled || !common.HasPermission(perm, common.PermFTPAccess) {
|
||||||
return nil, errors.New("user is not allowed to access via SFTP")
|
return nil, errors.New("user is not allowed to access via SFTP")
|
||||||
}
|
}
|
||||||
passHash := model.StaticHash(string(password))
|
passHash := model.StaticHash(string(password))
|
||||||
@@ -104,7 +107,8 @@ func (d *SftpDriver) PublicKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if userObj.Disabled || !userObj.CanFTPAccess() {
|
perm := common.MergeRolePermissions(userObj, userObj.BasePath)
|
||||||
|
if userObj.Disabled || !common.HasPermission(perm, common.PermFTPAccess) {
|
||||||
return nil, errors.New("user is not allowed to access via SFTP")
|
return nil, errors.New("user is not allowed to access via SFTP")
|
||||||
}
|
}
|
||||||
keys, _, err := op.GetSSHPublicKeyByUserId(userObj.ID, 1, -1)
|
keys, _, err := op.GetSSHPublicKeyByUserId(userObj.ID, 1, -1)
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"github.com/alist-org/alist/v3/internal/stream"
|
|
||||||
"github.com/alist-org/alist/v3/server/middlewares"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/stream"
|
||||||
|
"github.com/alist-org/alist/v3/server/middlewares"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/alist-org/alist/v3/server/webdav"
|
"github.com/alist-org/alist/v3/server/webdav"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -92,7 +95,19 @@ func WebDAVAuth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Disabled || !user.CanWebdavRead() {
|
reqPath := c.Param("path")
|
||||||
|
if reqPath == "" {
|
||||||
|
reqPath = "/"
|
||||||
|
}
|
||||||
|
reqPath, _ = url.PathUnescape(reqPath)
|
||||||
|
reqPath, err = user.JoinPath(reqPath)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusForbidden)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
perm := common.MergeRolePermissions(user, reqPath)
|
||||||
|
if user.Disabled || !common.HasPermission(perm, common.PermWebdavRead) {
|
||||||
if c.Request.Method == "OPTIONS" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
c.Set("user", guest)
|
c.Set("user", guest)
|
||||||
c.Next()
|
c.Next()
|
||||||
@@ -102,27 +117,27 @@ func WebDAVAuth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (c.Request.Method == "PUT" || c.Request.Method == "MKCOL") && (!user.CanWebdavManage() || !user.CanWrite()) {
|
if (c.Request.Method == "PUT" || c.Request.Method == "MKCOL") && (!common.HasPermission(perm, common.PermWebdavManage) || !common.HasPermission(perm, common.PermWrite)) {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.Method == "MOVE" && (!user.CanWebdavManage() || (!user.CanMove() && !user.CanRename())) {
|
if c.Request.Method == "MOVE" && (!common.HasPermission(perm, common.PermWebdavManage) || (!common.HasPermission(perm, common.PermMove) && !common.HasPermission(perm, common.PermRename))) {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.Method == "COPY" && (!user.CanWebdavManage() || !user.CanCopy()) {
|
if c.Request.Method == "COPY" && (!common.HasPermission(perm, common.PermWebdavManage) || !common.HasPermission(perm, common.PermCopy)) {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.Method == "DELETE" && (!user.CanWebdavManage() || !user.CanRemove()) {
|
if c.Request.Method == "DELETE" && (!common.HasPermission(perm, common.PermWebdavManage) || !common.HasPermission(perm, common.PermRemove)) {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.Method == "PROPPATCH" && !user.CanWebdavManage() {
|
if c.Request.Method == "PROPPATCH" && !common.HasPermission(perm, common.PermWebdavManage) {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// slashClean is equivalent to but slightly more efficient than
|
// slashClean is equivalent to but slightly more efficient than
|
||||||
@@ -34,10 +35,11 @@ func moveFiles(ctx context.Context, src, dst string, overwrite bool) (status int
|
|||||||
srcName := path.Base(src)
|
srcName := path.Base(src)
|
||||||
dstName := path.Base(dst)
|
dstName := path.Base(dst)
|
||||||
user := ctx.Value("user").(*model.User)
|
user := ctx.Value("user").(*model.User)
|
||||||
if srcDir != dstDir && !user.CanMove() {
|
perm := common.MergeRolePermissions(user, src)
|
||||||
|
if srcDir != dstDir && !common.HasPermission(perm, common.PermMove) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
if srcName != dstName && !user.CanRename() {
|
if srcName != dstName && !common.HasPermission(perm, common.PermRename) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
if srcDir == dstDir {
|
if srcDir == dstDir {
|
||||||
|
|||||||
Reference in New Issue
Block a user