转授权
This commit is contained in:
1 parent
39480e2f2e
commit
a8e85b7ac0
18 files changed
+284
-57
No files matched your search
@@ -28,5 +28,5 @@
|
||||
- 改动说明只在交流中记录。
|
||||
|
||||
## 当前重点
|
||||
- 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承。
|
||||
- 主线已完成:高级优化器 → 并行查询 → M1 会话隔离 → M2 快照事务 → M3 WAL 与崩溃恢复 → M4 表结构进 WAL → M5 系统表统一命名 → M6 检查点按 LSN 部分截断 → M7 事务写写冲突检测 → M8 事务保存点 → M9 角色继承 → M10 转授权。
|
||||
- 进度与计数见 `agent_credientials/project-plan.txt`,限制与风险见 `agent_credientials/known-issues.txt`。
|
||||
@@ -182,7 +182,9 @@ listRoles session = do
|
||||
, if null (roleMembers view) then "-" else T.intercalate ", " (roleMembers view)
|
||||
]
|
||||
-- | 拼一条授权说明
|
||||
grantText grant = grantPrivilege grant <> " on " <> grantObject grant
|
||||
grantText grant = grantPrivilege grant <> " on " <> grantObject grant <> optionText grant
|
||||
-- | 带转授权时补一段说明
|
||||
optionText grant = if grantable grant then " with grant option" else ""
|
||||
|
||||
-- | 用 show 转成 Text
|
||||
tshow :: Show a => a -> Text
|
||||
|
||||
@@ -415,7 +415,7 @@ checkResolvedWith db outer q = case q of
|
||||
DropUser name -> checkUserName name
|
||||
CreateRole name -> checkRoleName name
|
||||
DropRole name -> checkRoleName name
|
||||
GrantPrivileges privs obj role -> do
|
||||
GrantPrivileges privs obj role _ -> do
|
||||
mapM_ checkPrivilege privs
|
||||
checkGrantObject obj
|
||||
checkRoleName role
|
||||
|
||||
@@ -41,8 +41,8 @@ data Statement
|
||||
| CreateRole String
|
||||
-- | 删角色:连同它的授权与成员一起删
|
||||
| DropRole String
|
||||
-- | GRANT 权限 ON 表 TO 角色
|
||||
| GrantPrivileges [String] String String
|
||||
-- | GRANT 权限 ON 表 TO 角色,末位表示带 WITH GRANT OPTION
|
||||
| GrantPrivileges [String] String String Bool
|
||||
-- | REVOKE 权限 ON 表 FROM 角色
|
||||
| RevokePrivileges [String] String String
|
||||
-- | GRANT 角色 TO 用户:给用户加角色
|
||||
|
||||
@@ -464,6 +464,10 @@ privilegeName =
|
||||
grantObject :: Parser String
|
||||
grantObject = ("*" <$ symbol "*") <|> tableName
|
||||
|
||||
-- | 可选的 WITH GRANT OPTION
|
||||
grantOption :: Parser Bool
|
||||
grantOption = (True <$ try (keyword "with" *> keyword "grant" *> keyword "option")) <|> pure False
|
||||
|
||||
-- | 读 GRANT:带 ON 的授权限,不带的加角色
|
||||
grantStatement :: Parser Statement
|
||||
grantStatement = do
|
||||
@@ -474,7 +478,7 @@ grantStatement = do
|
||||
keyword "on"
|
||||
obj <- grantObject
|
||||
keyword "to"
|
||||
GrantPrivileges privs obj <$> userName
|
||||
GrantPrivileges privs obj <$> userName <*> grantOption
|
||||
, GrantRole <$> userName <*> (keyword "to" *> sepBy1 userName (symbol ","))
|
||||
]
|
||||
|
||||
|
||||
@@ -683,13 +683,21 @@ main = hspec $ do
|
||||
|
||||
it "parses privilege grants on one table, on another database's table or on everything" $ do
|
||||
parseStatement "GRANT SELECT ON users TO analyst"
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst")
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst" False)
|
||||
parseStatement "grant select, insert on users to analyst"
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT", "INSERT"] "users" "analyst")
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT", "INSERT"] "users" "analyst" False)
|
||||
parseStatement "GRANT ALL ON sales.orders TO analyst"
|
||||
`shouldBe` Right (GrantPrivileges ["ALL"] "sales.orders" "analyst")
|
||||
`shouldBe` Right (GrantPrivileges ["ALL"] "sales.orders" "analyst" False)
|
||||
parseStatement "GRANT DELETE ON * TO analyst"
|
||||
`shouldBe` Right (GrantPrivileges ["DELETE"] "*" "analyst")
|
||||
`shouldBe` Right (GrantPrivileges ["DELETE"] "*" "analyst" False)
|
||||
|
||||
it "parses WITH GRANT OPTION on a privilege grant" $ do
|
||||
parseStatement "GRANT SELECT ON users TO analyst WITH GRANT OPTION"
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst" True)
|
||||
parseStatement "grant select, insert on * to analyst with grant option"
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT", "INSERT"] "*" "analyst" True)
|
||||
parseStatement "GRANT SELECT ON users TO analyst WITH"
|
||||
`shouldSatisfy` isLeft
|
||||
|
||||
it "parses REVOKE symmetrically" $ do
|
||||
parseStatement "REVOKE SELECT ON users FROM analyst"
|
||||
@@ -717,7 +725,9 @@ main = hspec $ do
|
||||
(parseStatement "CREATE ROLE select" >>= prepare testDB) `shouldSatisfy` isLeft
|
||||
(parseStatement "DROP ROLE insert" >>= prepare testDB) `shouldSatisfy` isLeft
|
||||
(parseStatement "GRANT SELECT ON users TO analyst" >>= prepare testDB)
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst")
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst" False)
|
||||
(parseStatement "GRANT SELECT ON users TO analyst WITH GRANT OPTION" >>= prepare testDB)
|
||||
`shouldBe` Right (GrantPrivileges ["SELECT"] "users" "analyst" True)
|
||||
(parseStatement "GRANT SELECT ON users TO select" >>= prepare testDB) `shouldSatisfy` isLeft
|
||||
(parseStatement "REVOKE SELECT ON users FROM analyst" >>= prepare testDB)
|
||||
`shouldBe` Right (RevokePrivileges ["SELECT"] "users" "analyst")
|
||||
|
||||
@@ -230,7 +230,12 @@ pub fn reserved_table(table: &str) -> bool {
|
||||
}
|
||||
|
||||
/// 服务自己的角色与授权表名
|
||||
pub const PRIVILEGE_TABLES: [&str; 3] = ["__system_roles", "__system_grants", "__system_members"];
|
||||
pub const PRIVILEGE_TABLES: [&str; 4] = [
|
||||
"__system_roles",
|
||||
"__system_grants",
|
||||
"__system_grant_options",
|
||||
"__system_members",
|
||||
];
|
||||
|
||||
/// 是不是服务自己经请求通道读写的系统表
|
||||
pub fn internal_table(table: &str) -> bool {
|
||||
|
||||
@@ -1756,8 +1756,11 @@ fn privilege_tables_are_reachable_but_account_table_is_not() -> Result<(), Box<d
|
||||
let (_server, _data) = start_server();
|
||||
let mut c = connect()?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","database":"system","table":"__system_roles","columns":[{"name":"id","ty":"int"},{"name":"name","ty":"str"}]}))?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","database":"system","table":"__system_grant_options","columns":[{"name":"id","ty":"int"},{"name":"role","ty":"str"}]}))?;
|
||||
let scanned: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_roles"}"#))?;
|
||||
assert_eq!(scanned["status"], "rows", "{scanned}");
|
||||
let options: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_grant_options"}"#))?;
|
||||
assert_eq!(options["status"], "rows", "{options}");
|
||||
let denied: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","database":"system","table":"__system_users"}"#))?;
|
||||
assert_eq!(denied["status"], "error", "{denied}");
|
||||
assert!(
|
||||
@@ -1772,6 +1775,7 @@ fn privilege_tables_are_reachable_but_account_table_is_not() -> Result<(), Box<d
|
||||
.map(|one| one.as_str().unwrap_or(""))
|
||||
.collect();
|
||||
assert!(tables.contains(&"__system_roles"), "授权表要在字典里: {listed}");
|
||||
assert!(tables.contains(&"__system_grant_options"), "转授权表要在字典里: {listed}");
|
||||
assert!(!tables.contains(&"__system_users"), "账号表不能露面: {listed}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -341,12 +341,20 @@ dropRoleSql name = do
|
||||
pure ("DROP ROLE " ++ T.unpack role)
|
||||
|
||||
-- | 构造 GRANT 权限 ON 对象 TO 角色
|
||||
grantPrivilegesSql :: [Text] -> Text -> Text -> Either String String
|
||||
grantPrivilegesSql privileges object role = do
|
||||
grantPrivilegesSql :: [Text] -> Text -> Text -> Bool -> Either String String
|
||||
grantPrivilegesSql privileges object role withOption = do
|
||||
words' <- privilegeWords privileges
|
||||
target <- grantObject object
|
||||
who <- roleName role
|
||||
pure ("GRANT " ++ T.unpack (T.intercalate ", " words') ++ " ON " ++ T.unpack target ++ " TO " ++ T.unpack who)
|
||||
pure
|
||||
( "GRANT " ++ T.unpack (T.intercalate ", " words') ++ " ON " ++ T.unpack target
|
||||
++ " TO " ++ T.unpack who ++ tailText
|
||||
)
|
||||
where
|
||||
-- | 转授权后缀
|
||||
tailText
|
||||
| withOption = " WITH GRANT OPTION"
|
||||
| otherwise = ""
|
||||
|
||||
-- | 构造 REVOKE 权限 ON 对象 FROM 角色
|
||||
revokePrivilegesSql :: [Text] -> Text -> Text -> Either String String
|
||||
|
||||
@@ -26,11 +26,12 @@ import qualified Data.Text as T
|
||||
|
||||
-- server 线上协议的编解码:一行一条 JSON(UTF-8)。
|
||||
|
||||
-- | 一条角色授权:角色 + 具体权限 + 对象
|
||||
-- | 一条角色授权:角色 + 具体权限 + 对象,可带转授权
|
||||
data Grant = Grant
|
||||
{ grantRole :: Text
|
||||
, grantPrivilege :: Text
|
||||
, grantObject :: Text
|
||||
, grantable :: Bool
|
||||
}
|
||||
deriving (Show, Eq, Ord)
|
||||
|
||||
@@ -49,12 +50,17 @@ instance ToJSON Grant where
|
||||
[ "role" .= grantRole grant
|
||||
, "privilege" .= grantPrivilege grant
|
||||
, "object" .= grantObject grant
|
||||
, "grantable" .= grantable grant
|
||||
]
|
||||
|
||||
-- | 从 JSON 读一条授权
|
||||
instance FromJSON Grant where
|
||||
parseJSON = withObject "Grant" $ \o ->
|
||||
Grant <$> o .: "role" <*> o .: "privilege" <*> o .: "object"
|
||||
Grant
|
||||
<$> o .: "role"
|
||||
<*> o .: "privilege"
|
||||
<*> o .: "object"
|
||||
<*> o .:? "grantable" .!= False
|
||||
|
||||
-- | 一个角色转成 JSON
|
||||
instance ToJSON RoleView where
|
||||
|
||||
@@ -11,6 +11,7 @@ module ChuSQL.Server.Backend (
|
||||
ipcBackend,
|
||||
memoryBackend,
|
||||
membersTable,
|
||||
optionsTable,
|
||||
rolesTable,
|
||||
statementNeedsDatabase,
|
||||
statementTables,
|
||||
@@ -359,11 +360,15 @@ rolesTable = "__system_roles"
|
||||
grantsTable :: String
|
||||
grantsTable = "__system_grants"
|
||||
|
||||
-- | 带 grant option 的授权单独一张表
|
||||
optionsTable :: String
|
||||
optionsTable = "__system_grant_options"
|
||||
|
||||
membersTable :: String
|
||||
membersTable = "__system_members"
|
||||
|
||||
internalNames :: [String]
|
||||
internalNames = [rolesTable, grantsTable, membersTable]
|
||||
internalNames = [rolesTable, grantsTable, optionsTable, membersTable]
|
||||
|
||||
-- | 名字是否带内部表前缀
|
||||
reserved :: String -> Bool
|
||||
|
||||
@@ -14,6 +14,9 @@ module ChuSQL.Server.Catalog
|
||||
, readGrants
|
||||
, addGrant
|
||||
, removeGrant
|
||||
, readGrantOptions
|
||||
, addGrantOption
|
||||
, removeGrantOption
|
||||
, readMembers
|
||||
, addMember
|
||||
, removeMember
|
||||
@@ -22,7 +25,7 @@ module ChuSQL.Server.Catalog
|
||||
import ChuSQL.Core.Model (Row, Value (..))
|
||||
import ChuSQL.Interface.Actions (sqlLiteral)
|
||||
import ChuSQL.Interface.Protocol (Grant (..))
|
||||
import ChuSQL.Server.Backend (Backend (..), StatementResult (..), grantsTable, membersTable, rolesTable)
|
||||
import ChuSQL.Server.Backend (Backend (..), StatementResult (..), grantsTable, membersTable, optionsTable, rolesTable)
|
||||
import Control.Concurrent.MVar (MVar, newMVar, withMVar)
|
||||
import Data.List (intercalate, isInfixOf)
|
||||
import Data.Text (Text)
|
||||
@@ -75,6 +78,7 @@ ensureCatalog catalog = eachAction tableDefs (createTable catalog)
|
||||
tableDefs =
|
||||
[ (rolesTable, "(name VARCHAR(64))")
|
||||
, (grantsTable, "(role VARCHAR(64), privilege VARCHAR(16), object VARCHAR(128))")
|
||||
, (optionsTable, "(role VARCHAR(64), privilege VARCHAR(16), object VARCHAR(128))")
|
||||
, (membersTable, "(role VARCHAR(64), member VARCHAR(64))")
|
||||
]
|
||||
|
||||
@@ -111,9 +115,10 @@ dropRole catalog role = do
|
||||
second <- exec catalog ("DELETE FROM " ++ grantsTable ++ " WHERE role = " ++ roleLiteral role)
|
||||
third <- exec catalog ("DELETE FROM " ++ membersTable ++ " WHERE role = " ++ roleLiteral role)
|
||||
fourth <- exec catalog ("DELETE FROM " ++ membersTable ++ " WHERE member = " ++ roleLiteral role)
|
||||
pure (sequence_ [first, second, third, fourth])
|
||||
fifth <- exec catalog ("DELETE FROM " ++ optionsTable ++ " WHERE role = " ++ roleLiteral role)
|
||||
pure (sequence_ [first, second, third, fourth, fifth])
|
||||
|
||||
-- | 全部授权
|
||||
-- | 全部授权,都不带 grant option
|
||||
readGrants :: Catalog -> IO (Either String [Grant])
|
||||
readGrants catalog = fmap (fmap (map grantOf)) (readRows catalog grantsTable)
|
||||
|
||||
@@ -127,7 +132,23 @@ addGrant catalog role privilege object = do
|
||||
|
||||
-- | 取消一条授权
|
||||
removeGrant :: Catalog -> Text -> Text -> Text -> IO (Either String ())
|
||||
removeGrant catalog role privilege object = exec catalog (deleteGrantSql role privilege object)
|
||||
removeGrant catalog role privilege object = exec catalog (deleteGrantSql grantsTable role privilege object)
|
||||
|
||||
-- | 全部带 grant option 的授权
|
||||
readGrantOptions :: Catalog -> IO (Either String [Grant])
|
||||
readGrantOptions catalog = fmap (fmap (map optionOf)) (readRows catalog optionsTable)
|
||||
|
||||
-- | 记一条带 grant option 的授权,同名的先清掉
|
||||
addGrantOption :: Catalog -> Text -> Text -> Text -> IO (Either String ())
|
||||
addGrantOption catalog role privilege object = do
|
||||
cleared <- removeGrantOption catalog role privilege object
|
||||
case cleared of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> exec catalog (insertSql optionsTable ["role", "privilege", "object"] [normalizeRole role, privilege, object])
|
||||
|
||||
-- | 取消一条带 grant option 的授权
|
||||
removeGrantOption :: Catalog -> Text -> Text -> Text -> IO (Either String ())
|
||||
removeGrantOption catalog role privilege object = exec catalog (deleteGrantSql optionsTable role privilege object)
|
||||
|
||||
-- | 全部成员关系
|
||||
readMembers :: Catalog -> IO (Either String [(Text, Text)])
|
||||
@@ -164,16 +185,21 @@ grantOf row =
|
||||
{ grantRole = normalizeRole (cell "role" row)
|
||||
, grantPrivilege = T.toLower (T.strip (cell "privilege" row))
|
||||
, grantObject = T.toLower (T.strip (cell "object" row))
|
||||
, grantable = False
|
||||
}
|
||||
|
||||
-- | 一行带 grant option 的授权记录
|
||||
optionOf :: Row -> Grant
|
||||
optionOf row = (grantOf row) {grantable = True}
|
||||
|
||||
-- | 一行成员关系
|
||||
memberOf :: Row -> (Text, Text)
|
||||
memberOf row = (normalizeRole (cell "role" row), normalizeUser (cell "member" row))
|
||||
|
||||
-- | 拼一条删授权的语句
|
||||
deleteGrantSql :: Text -> Text -> Text -> String
|
||||
deleteGrantSql role privilege object =
|
||||
"DELETE FROM " ++ grantsTable ++ " WHERE role = " ++ roleLiteral role
|
||||
deleteGrantSql :: String -> Text -> Text -> Text -> String
|
||||
deleteGrantSql table role privilege object =
|
||||
"DELETE FROM " ++ table ++ " WHERE role = " ++ roleLiteral role
|
||||
++ " AND privilege = " ++ literal privilege ++ " AND object = " ++ literal object
|
||||
|
||||
-- | 拼一条插入语句
|
||||
|
||||
@@ -29,6 +29,7 @@ import ChuSQL.Server.Backend (
|
||||
import ChuSQL.Server.Catalog (
|
||||
Catalog,
|
||||
addGrant,
|
||||
addGrantOption,
|
||||
addMember,
|
||||
dropRole,
|
||||
eachAction,
|
||||
@@ -37,10 +38,12 @@ import ChuSQL.Server.Catalog (
|
||||
normalizeObject,
|
||||
normalizeRole,
|
||||
normalizeUser,
|
||||
readGrantOptions,
|
||||
readGrants,
|
||||
readMembers,
|
||||
readRoleNames,
|
||||
removeGrant,
|
||||
removeGrantOption,
|
||||
removeMember,
|
||||
withCatalogLock,
|
||||
ensureCatalog,
|
||||
@@ -68,7 +71,7 @@ data Membership = Membership
|
||||
data PrivilegeCommand
|
||||
= CreateRoleCommand Text
|
||||
| DropRoleCommand Text
|
||||
| GrantPrivilegesCommand [Text] Text Text
|
||||
| GrantPrivilegesCommand [Text] Text Text Bool
|
||||
| RevokePrivilegesCommand [Text] Text Text
|
||||
| GrantRoleCommand Text [Text]
|
||||
| RevokeRoleCommand Text [Text]
|
||||
@@ -87,24 +90,24 @@ privilegeCommand :: Statement -> Maybe PrivilegeCommand
|
||||
privilegeCommand stmt = case stmt of
|
||||
CreateRole name -> Just (CreateRoleCommand (T.pack name))
|
||||
DropRole name -> Just (DropRoleCommand (T.pack name))
|
||||
GrantPrivileges privs obj role ->
|
||||
Just (GrantPrivilegesCommand (map T.toLower (map T.pack privs)) (T.pack obj) (T.pack role))
|
||||
GrantPrivileges privs obj role withOption ->
|
||||
Just (GrantPrivilegesCommand (map T.toLower (map T.pack privs)) (T.pack obj) (T.pack role) withOption)
|
||||
RevokePrivileges privs obj role ->
|
||||
Just (RevokePrivilegesCommand (map T.toLower (map T.pack privs)) (T.pack obj) (T.pack role))
|
||||
GrantRole role members -> Just (GrantRoleCommand (T.pack role) (map T.pack members))
|
||||
RevokeRole role members -> Just (RevokeRoleCommand (T.pack role) (map T.pack members))
|
||||
_ -> Nothing
|
||||
|
||||
-- | 执行管理命令:只有管理员能跑,命令提到的角色必须已经存在
|
||||
-- | 执行管理命令:管理员全通,普通身份只能转授自己拿到的权限
|
||||
runPrivilegeCommand :: Privileges -> Principal -> Text -> PrivilegeCommand -> IO (Either PrivilegeError ())
|
||||
runPrivilegeCommand service principal database command
|
||||
| not (principalIsRoot principal) = pure (Left forbidden)
|
||||
| needsDatabase = pure (Left (PrivilegeError "no_database" "no database selected"))
|
||||
| otherwise = withTables service (applyCommand service database command)
|
||||
| principalIsRoot principal = withTables service (applyCommand service database command)
|
||||
| otherwise = withTables service (delegateCommand service principal database command)
|
||||
where
|
||||
-- | 授权到具体对象时需要先选库
|
||||
needsDatabase = T.null database && case command of
|
||||
GrantPrivilegesCommand _ object _ -> T.strip object /= "*"
|
||||
GrantPrivilegesCommand _ object _ _ -> T.strip object /= "*"
|
||||
RevokePrivilegesCommand _ object _ -> T.strip object /= "*"
|
||||
_ -> False
|
||||
|
||||
@@ -164,10 +167,18 @@ requiredActions stmt = case stmt of
|
||||
|
||||
-- | 每项 (表, 权限) 都要被某条授权覆盖
|
||||
checkAll :: Text -> [Grant] -> [(Text, Text)] -> Either PrivilegeError ()
|
||||
checkAll database grants needed = case [pair | pair@(table, privilege) <- needed, not (covered table privilege)] of
|
||||
checkAll database grants needed = checkCovered database grants needed "permission denied: "
|
||||
|
||||
-- | 转授权要查 grant option,报错口径与普通鉴权分开
|
||||
checkOptions :: Text -> [Grant] -> [(Text, Text)] -> Either PrivilegeError ()
|
||||
checkOptions database grants needed = checkCovered database grants needed "grant option required: "
|
||||
|
||||
-- | 覆盖检查的公共实现
|
||||
checkCovered :: Text -> [Grant] -> [(Text, Text)] -> Text -> Either PrivilegeError ()
|
||||
checkCovered database grants needed prefix = case [pair | pair@(table, privilege) <- needed, not (covered table privilege)] of
|
||||
[] -> Right ()
|
||||
((table, privilege) : _) ->
|
||||
Left (PrivilegeError "forbidden" ("permission denied: " <> T.toUpper privilege <> " ON " <> table))
|
||||
Left (PrivilegeError "forbidden" (prefix <> T.toUpper privilege <> " ON " <> table))
|
||||
where
|
||||
-- | 某一项是否被授权覆盖
|
||||
covered table privilege =
|
||||
@@ -176,19 +187,25 @@ checkAll database grants needed = case [pair | pair@(table, privilege) <- needed
|
||||
(\grant -> (grantObject grant == "*" || grantObject grant == target) && grantPrivilege grant == privilege)
|
||||
grants
|
||||
|
||||
-- | 一个用户实际能用的授权:所属角色加上继承来的
|
||||
-- | 一个用户实际能用的授权:含角色继承
|
||||
grantsOfUser :: Privileges -> Text -> IO (Either PrivilegeError [Grant])
|
||||
grantsOfUser service user = do
|
||||
members <- catalog (readMembers (pvCatalog service))
|
||||
grants <- catalog (readGrants (pvCatalog service))
|
||||
options <- catalog (readGrantOptions (pvCatalog service))
|
||||
roles <- roleNames service
|
||||
pure $ do
|
||||
memberRows <- members
|
||||
grantRows <- grants
|
||||
optionRows <- options
|
||||
known <- roles
|
||||
let edges = roleEdges memberRows known
|
||||
mine = closureUp edges [role | (role, member) <- memberRows, member == normalizeUser user]
|
||||
pure [grant | grant <- grantRows, grantRole grant `elem` mine]
|
||||
pure [grant | grant <- grantRows ++ optionRows, grantRole grant `elem` mine]
|
||||
|
||||
-- | 一个用户手里的 grant option,只有这些能再转授
|
||||
optionsOfUser :: Privileges -> Text -> IO (Either PrivilegeError [Grant])
|
||||
optionsOfUser service user = fmap (fmap (filter grantable)) (grantsOfUser service user)
|
||||
|
||||
-- | 角色到角色的成员边:父角色在前
|
||||
roleEdges :: [(Text, Text)] -> [Text] -> [(Text, Text)]
|
||||
@@ -203,11 +220,28 @@ closureUp edges = go
|
||||
let more = [parent | (parent, child) <- edges, child `elem` known, parent `notElem` known]
|
||||
in if null more then known else go (known ++ more)
|
||||
|
||||
-- | 一个角色挂着的授权
|
||||
-- | 一个角色挂着的授权,带 grant option 的会标出来
|
||||
roleGrantsOf :: Privileges -> Text -> IO (Either PrivilegeError [Grant])
|
||||
roleGrantsOf service role = do
|
||||
grants <- catalog (readGrants (pvCatalog service))
|
||||
pure (filter (\grant -> grantRole grant == normalizeRole role) <$> grants)
|
||||
options <- catalog (readGrantOptions (pvCatalog service))
|
||||
pure (do
|
||||
rows <- grants
|
||||
optionRows <- options
|
||||
let mine = filter (\grant -> grantRole grant == normalizeRole role) rows
|
||||
pure (markOptions mine optionRows))
|
||||
|
||||
-- | 给授权标上有没有对应的 grant option
|
||||
markOptions :: [Grant] -> [Grant] -> [Grant]
|
||||
markOptions grants options = [grant {grantable = hasOption grant} | grant <- grants]
|
||||
where
|
||||
-- | 有没有一条同角色同权限同对象的 option
|
||||
hasOption grant = any (same grant) options
|
||||
-- | 两条授权指向同一处
|
||||
same one other =
|
||||
grantRole one == grantRole other
|
||||
&& grantPrivilege one == grantPrivilege other
|
||||
&& grantObject one == grantObject other
|
||||
|
||||
-- | 角色名清单(统一小写)
|
||||
roleNames :: Privileges -> IO (Either PrivilegeError [Text])
|
||||
@@ -244,20 +278,20 @@ applyCommand service database command = case command of
|
||||
case allowed of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> catalog (dropRole (pvCatalog service) role)
|
||||
GrantPrivilegesCommand privileges object role -> do
|
||||
GrantPrivilegesCommand privileges object role withOption -> do
|
||||
allowed <- requireRole service role
|
||||
case allowed of
|
||||
Left err -> pure (Left err)
|
||||
Right () ->
|
||||
eachAction (expandPrivileges privileges) $ \privilege ->
|
||||
catalog (addGrant (pvCatalog service) role privilege (normalizeObject database object))
|
||||
grantOne service withOption role privilege (normalizeObject database object)
|
||||
RevokePrivilegesCommand privileges object role -> do
|
||||
allowed <- requireRole service role
|
||||
case allowed of
|
||||
Left err -> pure (Left err)
|
||||
Right () ->
|
||||
eachAction (expandPrivileges privileges) $ \privilege ->
|
||||
catalog (removeGrant (pvCatalog service) role privilege (normalizeObject database object))
|
||||
revokeOne service role privilege (normalizeObject database object)
|
||||
GrantRoleCommand role members -> do
|
||||
allowed <- requireRole service role
|
||||
case allowed of
|
||||
@@ -273,6 +307,43 @@ applyCommand service database command = case command of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> eachAction users $ \user -> catalog (removeMember (pvCatalog service) role user)
|
||||
|
||||
-- | 落一条授权,带 grant option 的再落一条转授权
|
||||
grantOne :: Privileges -> Bool -> Text -> Text -> Text -> IO (Either PrivilegeError ())
|
||||
grantOne service withOption role privilege object = do
|
||||
written <- catalog (addGrant (pvCatalog service) role privilege object)
|
||||
case written of
|
||||
Left err -> pure (Left err)
|
||||
Right ()
|
||||
| withOption -> catalog (addGrantOption (pvCatalog service) role privilege object)
|
||||
| otherwise -> pure (Right ())
|
||||
|
||||
-- | 收一条授权,连同它的 grant option
|
||||
revokeOne :: Privileges -> Text -> Text -> Text -> IO (Either PrivilegeError ())
|
||||
revokeOne service role privilege object = do
|
||||
dropped <- catalog (removeGrant (pvCatalog service) role privilege object)
|
||||
case dropped of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> catalog (removeGrantOption (pvCatalog service) role privilege object)
|
||||
|
||||
-- | 普通身份转授权:只放行 GRANT
|
||||
delegateCommand :: Privileges -> Principal -> Text -> PrivilegeCommand -> IO (Either PrivilegeError ())
|
||||
delegateCommand service principal database command = case command of
|
||||
GrantPrivilegesCommand privileges object role _ -> do
|
||||
allowed <- requireRole service role
|
||||
case allowed of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> do
|
||||
owned <- optionsOfUser service (principalName principal)
|
||||
case owned >>= \found -> checkOptions database found needed of
|
||||
Left err -> pure (Left err)
|
||||
Right () -> applyCommand service database command
|
||||
_ -> pure (Left forbidden)
|
||||
where
|
||||
-- | 命令里每个 (对象, 权限) 都要对得上
|
||||
needed = case command of
|
||||
GrantPrivilegesCommand privileges object _ _ -> [(object, privilege) | privilege <- expandPrivileges privileges]
|
||||
_ -> []
|
||||
|
||||
-- | 加一条成员边:名字是角色就查环,否则当用户加
|
||||
grantMember :: Privileges -> [Text] -> Text -> Text -> IO (Either PrivilegeError ())
|
||||
grantMember service roles role member = do
|
||||
|
||||
+60
-10
@@ -507,14 +507,14 @@ privilegeSpec = describe "server privileges" $ do
|
||||
|
||||
it "grants a privilege on a table and shows it in the role view" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
granted <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
granted <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
granted `shouldBe` Right ()
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[Grant "reader" "select" "test.users"]]
|
||||
fmap (map roleGrants) views `shouldBe` Right [[Grant "reader" "select" "test.users" False]]
|
||||
|
||||
it "expands ALL and keeps a star object as is" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["all"] "*" "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["all"] "*" "reader" False)
|
||||
views <- listRoleViews service
|
||||
fmap (concatMap (map grantPrivilege) . map roleGrants) views
|
||||
`shouldBe` Right ["select", "insert", "update", "delete"]
|
||||
@@ -522,11 +522,47 @@ privilegeSpec = describe "server privileges" $ do
|
||||
|
||||
it "revokes a privilege again" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service root testDatabase (RevokePrivilegesCommand ["select"] "users" "reader")
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[]]
|
||||
|
||||
it "marks a grant that carries the grant option" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" True)
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[Grant "reader" "select" "test.users" True]]
|
||||
|
||||
it "refuses a non-administrator without the grant option" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
refused <- runPrivilegeCommand service (Ordinary testAccount) testDatabase (GrantPrivilegesCommand ["select"] "users" "writer" False)
|
||||
refused `shouldBe` Left (PrivilegeError "forbidden" "grant option required: SELECT ON users")
|
||||
|
||||
it "lets a grant option holder pass the privilege on" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" True)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
passed <- runPrivilegeCommand service (Ordinary testAccount) testDatabase (GrantPrivilegesCommand ["select"] "users" "writer" False)
|
||||
passed `shouldBe` Right ()
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "writer" ["bob"])
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[Grant "reader" "select" "test.users" True], [Grant "writer" "select" "test.users" False]]
|
||||
|
||||
it "stops the pass-on once the grant option is revoked" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" True)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
_ <- runPrivilegeCommand service root testDatabase (RevokePrivilegesCommand ["select"] "users" "reader")
|
||||
refused <- runPrivilegeCommand service (Ordinary testAccount) testDatabase (GrantPrivilegesCommand ["select"] "users" "writer" False)
|
||||
refused `shouldBe` Left (PrivilegeError "forbidden" "grant option required: SELECT ON users")
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[], []]
|
||||
|
||||
it "adds and removes members" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
@@ -539,7 +575,7 @@ privilegeSpec = describe "server privileges" $ do
|
||||
|
||||
it "drops a role together with its grants and members" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
dropped <- runPrivilegeCommand service root testDatabase (DropRoleCommand "reader")
|
||||
dropped `shouldBe` Right ()
|
||||
@@ -549,7 +585,7 @@ privilegeSpec = describe "server privileges" $ do
|
||||
it "inherits the grants of another role" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["writer"])
|
||||
linked <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "writer" ["alice"])
|
||||
linked `shouldBe` Right ()
|
||||
@@ -579,7 +615,7 @@ privilegeSpec = describe "server privileges" $ do
|
||||
it "drops the inherited edge together with the role" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "reader" ["writer"])
|
||||
_ <- runPrivilegeCommand service root testDatabase (GrantRoleCommand "writer" ["alice"])
|
||||
dropped <- runPrivilegeCommand service root testDatabase (DropRoleCommand "reader")
|
||||
@@ -590,12 +626,12 @@ privilegeSpec = describe "server privileges" $ do
|
||||
fmap (map roleMembers) views `shouldBe` Right [["alice"]]
|
||||
|
||||
it "refuses a command on a role that does not exist" $ withPrivileges $ \service root -> do
|
||||
missing <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "ghost")
|
||||
missing <- runPrivilegeCommand service root testDatabase (GrantPrivilegesCommand ["select"] "users" "ghost" False)
|
||||
missing `shouldBe` Left (PrivilegeError "not_found" "unknown role: ghost")
|
||||
|
||||
it "refuses a table grant without a selected database" $ withPrivileges $ \service root -> do
|
||||
_ <- runPrivilegeCommand service root testDatabase (CreateRoleCommand "reader")
|
||||
refused <- runPrivilegeCommand service root "" (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
refused <- runPrivilegeCommand service root "" (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
refused `shouldBe` Left (PrivilegeError "no_database" "no database selected")
|
||||
|
||||
it "refuses a non-administrator" $ withPrivileges $ \service _ -> do
|
||||
@@ -631,12 +667,26 @@ privilegeSpec = describe "server privileges" $ do
|
||||
service <- newPrivileges base
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantPrivilegesCommand ["select"] "users" "reader")
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" False)
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantRoleCommand "reader" ["writer"])
|
||||
linked <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantRoleCommand "writer" ["alice"])
|
||||
linked `shouldBe` Right ()
|
||||
authorizeSql service (Ordinary testAccount) "SELECT * FROM users" `shouldReturn` Right ()
|
||||
|
||||
it "keeps a grant option on the real storage" $ withIpcStorage $ do
|
||||
base <- ipcBackend
|
||||
service <- newPrivileges base
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (CreateRoleCommand "reader")
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (CreateRoleCommand "writer")
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantPrivilegesCommand ["select"] "users" "reader" True)
|
||||
_ <- runPrivilegeCommand service (Root testRootName) testDatabase (GrantRoleCommand "reader" ["alice"])
|
||||
views <- listRoleViews service
|
||||
fmap (map roleGrants) views `shouldBe` Right [[Grant "reader" "select" "test.users" True], []]
|
||||
passed <- runPrivilegeCommand service (Ordinary testAccount) testDatabase (GrantPrivilegesCommand ["select"] "users" "writer" False)
|
||||
passed `shouldBe` Right ()
|
||||
after <- listRoleViews service
|
||||
fmap (map roleGrants) after `shouldBe` Right [[Grant "reader" "select" "test.users" True], [Grant "writer" "select" "test.users" False]]
|
||||
|
||||
-- 真存储夹具:一个用例一份数据目录,跑完关链路删干净
|
||||
|
||||
-- | 本次用例的数据目录(用例串行跑,固定名够用)
|
||||
|
||||
@@ -1212,6 +1212,7 @@ grantJson grant =
|
||||
object
|
||||
[ "privilege" .= grantPrivilege grant
|
||||
, "object" .= grantObject grant
|
||||
, "grantable" .= grantable grant
|
||||
]
|
||||
|
||||
-- | 建角色
|
||||
@@ -1236,7 +1237,8 @@ grantRoleH env = do
|
||||
role <- pathParam "name"
|
||||
withJsonObject env $ \o -> case grantFields o of
|
||||
Left err -> reply400 "bad_request" err
|
||||
Right (privileges, objectName) -> runPrivilegeSql ws (grantPrivilegesSql privileges objectName role)
|
||||
Right (privileges, objectName, withOption) ->
|
||||
runPrivilegeSql ws (grantPrivilegesSql privileges objectName role withOption)
|
||||
|
||||
-- | 收角色的权限
|
||||
revokeRoleH :: AppEnv -> ActionM ()
|
||||
@@ -1245,7 +1247,7 @@ revokeRoleH env = do
|
||||
role <- pathParam "name"
|
||||
withJsonObject env $ \o -> case grantFields o of
|
||||
Left err -> reply400 "bad_request" err
|
||||
Right (privileges, objectName) -> runPrivilegeSql ws (revokePrivilegesSql privileges objectName role)
|
||||
Right (privileges, objectName, _) -> runPrivilegeSql ws (revokePrivilegesSql privileges objectName role)
|
||||
|
||||
-- | 把用户加进角色
|
||||
addRoleMemberH :: AppEnv -> ActionM ()
|
||||
@@ -1264,14 +1266,18 @@ removeRoleMemberH env = do
|
||||
user <- pathParam "user"
|
||||
runPrivilegeSql ws (revokeRoleSql role [user])
|
||||
|
||||
-- | 权限(一条或一串)与对象
|
||||
grantFields :: A.Object -> Either Text ([Text], Text)
|
||||
-- | 权限(一条或一串)、对象与是否带转授权
|
||||
grantFields :: A.Object -> Either Text ([Text], Text, Bool)
|
||||
grantFields o = do
|
||||
objectName <- textField "object" o
|
||||
privileges <- case KM.lookup "privileges" o of
|
||||
Just (A.Array xs) -> mapM stringValue (V.toList xs)
|
||||
_ -> Left "missing or non-array field: privileges"
|
||||
if null privileges then Left "at least one privilege is required" else pure (privileges, objectName)
|
||||
withOption <- case KM.lookup "grantOption" o of
|
||||
Nothing -> Right False
|
||||
Just (A.Bool flag) -> Right flag
|
||||
_ -> Left "grantOption must be a boolean"
|
||||
if null privileges then Left "at least one privilege is required" else pure (privileges, objectName, withOption)
|
||||
|
||||
-- | 成员(一条或一串)
|
||||
memberFields :: A.Object -> Either Text [Text]
|
||||
|
||||
@@ -262,6 +262,11 @@ asInt :: A.Value -> Int
|
||||
asInt (A.Number n) = round n
|
||||
asInt _ = -1
|
||||
|
||||
-- | 当布尔看
|
||||
asBool :: A.Value -> Bool
|
||||
asBool (A.Bool b) = b
|
||||
asBool _ = False
|
||||
|
||||
-- | 第 i 个元素,越界给 Nothing
|
||||
nth :: Int -> [a] -> Maybe a
|
||||
nth i xs = case drop i xs of
|
||||
@@ -852,6 +857,28 @@ roleSpec env = describe "roles and grants" $ do
|
||||
dropped <- deleteAs hs "/api/roles/analyst"
|
||||
expectStatusWith "drop role" dropped 200
|
||||
|
||||
it "carries the grant option through the API" $ do
|
||||
hs <- adminHeaders
|
||||
ensureDatabase hs testDatabaseName
|
||||
created <- postAs hs "/api/roles" (encode (object ["name" .= ("passer" :: Text)]))
|
||||
expectStatusWith "create passer" created 200
|
||||
granted <-
|
||||
postAs
|
||||
(withDb testDatabaseName hs)
|
||||
"/api/roles/passer/grants"
|
||||
(encode (object ["object" .= testDatabaseName, "privileges" .= (["select"] :: [Text]), "grantOption" .= True]))
|
||||
expectStatusWith "grant with option" granted 200
|
||||
listed <- getAs hs "/api/roles"
|
||||
expectStatusWith "list roles" listed 200
|
||||
let matching = [view | view <- items (jsonBody listed), asText (at "name" view) == "passer"]
|
||||
check ((concatMap (map (asBool . at "grantable") . items . at "grants") matching) `shouldBe` [True])
|
||||
refused <-
|
||||
postAs
|
||||
hs
|
||||
"/api/roles/passer/grants"
|
||||
(encode (object ["object" .= testDatabaseName, "privileges" .= (["select"] :: [Text]), "grantOption" .= ("yes" :: Text)]))
|
||||
expectStatusWith "non-boolean grant option" refused 400
|
||||
|
||||
-- SQL 控制台 -----------------------------------------------------------------
|
||||
|
||||
-- | SQL 控制台用例
|
||||
|
||||
@@ -53,6 +53,6 @@ chusql-server
|
||||
|
||||
WAL 负责崩溃恢复:每条记录带自增 LSN,一组写操作与一条提交标记一次落盘;每组提交在数据和字典都刷盘之后把 `wal.checkpoint` 推进到这条提交的 LSN,含义是「到此为止的改动都已落盘」,再按这个边界丢掉日志前缀——所以日志里通常只有当组在飞的记录。进程重启时只重放检查点之后的记录,按标记分组取已提交的组、丢弃没有标记的整组,重放前逐条做幂等检查作为第二道保险,结束后再把检查点推到最后一条提交。LSN 从检查点与文件尾里较大的那个续号,跨重启单调,`SystemStatus` 应答里的 `last_lsn` 报告该检查点 LSN。建表、删表、建索引、删索引与数据改动走同一条 WAL,恢复时一并重放。
|
||||
|
||||
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、成员三张表(`__system_roles` / `__system_grants` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。
|
||||
系统数据库及部分管理操作仅允许管理员访问。系统表统一用 `__system_` 前缀:账号表 `__system_users` 对语句通道隐藏,角色、授权、成员三张表(`__system_roles` / `__system_grants` / `__system_members`)只在 `system` 库里可见,权限服务自身也通过 SQL 读写它们;存储启动时把旧名 `sys_roles` / `sys_grants` / `sys_members` 连同数据与索引文件一起迁移过来。角色支持互相继承:`GRANT 角色 A TO 角色 B` 记一条 B 属于 A 的成员关系,判定权限时沿成员关系算出向上闭包再取并集;写入时拒绝成环与自继承,删角色会清掉它两个方向的成员关系。带 `WITH GRANT OPTION` 的授权记在第四张表 `__system_grant_options` 里(同样的角色、权限、对象三列,单独的列存不下就单独一张表,省掉系统表的结构迁移):普通身份只要手里那条授权带 grant option 就能把同一项权限转授给别的角色,`REVOKE` 仍然只允许管理员,收回授权时连它的 grant option 一起收掉,但不级联收回别人已经转授出去的授权。授权命令由权限服务在会话层之外直接执行,`GRANT` 命令会先按当前库把对象补全成 `库.表` 再落盘,因此判断转授权资格与实际写入用的是同一个对象名。
|
||||
|
||||
服务监听地址、端口和请求限制可在 `chusql.toml` 的 `[server]` 分区中配置。
|
||||
@@ -91,6 +91,7 @@ DROP USER alice;
|
||||
CREATE ROLE analyst;
|
||||
CREATE ROLE reviewer;
|
||||
GRANT SELECT ON orders TO analyst;
|
||||
GRANT SELECT ON orders TO reviewer WITH GRANT OPTION;
|
||||
GRANT analyst TO reviewer;
|
||||
GRANT reviewer TO alice;
|
||||
REVOKE SELECT ON orders FROM analyst;
|
||||
@@ -105,6 +106,8 @@ DROP ROLE analyst;
|
||||
|
||||
管理员拥有完整权限;数据库、表、索引等结构管理操作仅允许管理员执行。
|
||||
|
||||
`GRANT ... WITH GRANT OPTION` 让拿到的授权可以再转授:普通身份只有手里那条授权带 grant option 时才能执行 `GRANT`,报错是 `grant option required: <权限> ON <表>`;`REVOKE` 仍然只允许管理员,收回授权会连它的 grant option 一起收掉,且不会级联收回别人转授出去的授权。转授权随角色继承一起传递。
|
||||
|
||||
系统数据库 `system` 仅允许管理员访问。
|
||||
|
||||
## chusql-web
|
||||
|
||||
Reference in new issue
Block a user