Use Permissions type in the API

This commit is contained in:
Saku Laesvuori 2024-01-19 15:43:04 +02:00
parent c0c71b1203
commit b1d02f68a0
Signed by: slaesvuo
GPG Key ID: 257D284A2A1D3A32
9 changed files with 84 additions and 65 deletions

View File

@ -114,7 +114,7 @@ authBearer Nothing m = m
authBearer (Just (BearerToken bearer)) m = do authBearer (Just (BearerToken bearer)) m = do
let getUserPermissions = do let getUserPermissions = do
Right DBToken {..} <- lift $ dbGetTokenBySecret bearer Right DBToken {..} <- lift $ dbGetTokenBySecret bearer
permissions' <- fromMaybe mempty . readPermission <$> lift dbTokenPermissions permissions' <- lift dbTokenPermissions
DBUser {..} <- lift dbTokenUser DBUser {..} <- lift dbTokenUser
userID <- lift dbUserId userID <- lift dbUserId
pure (Just userID, permissions') pure (Just userID, permissions')
@ -140,8 +140,7 @@ authBasic :: Maybe BasicAuth -> ActionT LText APIM a -> ActionT LText APIM a
authBasic Nothing m = m authBasic Nothing m = m
authBasic (Just basic) m = do authBasic (Just basic) m = do
DBUser {..} <- verifyBasic basic DBUser {..} <- verifyBasic basic
permissions <- readPermission <$> lift dbUserPermissions >>= permissions <- lift dbUserPermissions
fromMaybeFail status500 "Internal server error"
userID <- lift dbUserId userID <- lift dbUserId
flip local m $ \state -> state flip local m $ \state -> state
{ stateCurrentUser = Just userID { stateCurrentUser = Just userID
@ -166,7 +165,7 @@ newtype APIM a = APIM (ReaderT RequestState IO a)
data RequestState = RequestState data RequestState = RequestState
{ stateCurrentUser :: Maybe UserID { stateCurrentUser :: Maybe UserID
, statePermissions :: Map Scope Permission , statePermissions :: Permissions
, stateConfig :: Config , stateConfig :: Config
} }
@ -214,28 +213,13 @@ instance MonadRandom APIM where
getRandomBytes = liftIO . getRandomBytes getRandomBytes = liftIO . getRandomBytes
instance MonadPermissions APIM where instance MonadPermissions APIM where
currentPermissions = show <$> asks statePermissions currentPermissions = asks statePermissions
defaultPermissions = pure $ show $ (fromList [(OwnProfile, ReadWrite)] :: Map Scope Permission) defaultPermissions = pure $ Permissions $ fromList [(OwnProfile, ReadWrite)]
toPermissions = pure . fmap show . readPermission
hasPermission scope permission = (>= permission) <$> findPermission scope
where findPermission :: Scope -> APIM Permission
findPermission scope@(Profile user) = selfPermissions scope user OwnProfile
findPermission scope@(Tokens user) = selfPermissions scope user OwnTokens
findPermission scope = findPermission' scope <$> asks statePermissions
findPermission' :: Scope -> Map Scope Permission -> Permission
findPermission' = findWithDefault None
selfPermissions :: Scope -> UserID -> Scope -> APIM Permission
selfPermissions scope user own = do
isSelf <- (Just user ==) <$> currentUser
let f = if isSelf then max <$> findPermission' own <*> findPermission' scope
else findPermission' scope
f <$> asks statePermissions
runAPIM :: Config -> APIM a -> IO a runAPIM :: Config -> APIM a -> IO a
runAPIM config (APIM m) = runReaderT m RequestState runAPIM config (APIM m) = runReaderT m RequestState
{ stateCurrentUser = Nothing { stateCurrentUser = Nothing
, statePermissions = fromList [] , statePermissions = Permissions $ fromList []
, stateConfig = config , stateConfig = config
} }

View File

@ -24,6 +24,7 @@ import "cryptonite" Crypto.Random (getRandomBytes, MonadRandom)
import qualified "base64" Data.ByteString.Base64 as B64 import qualified "base64" Data.ByteString.Base64 as B64
import Control.Monad.Except (MonadError, throwError, catchError) import Control.Monad.Except (MonadError, throwError, catchError)
import qualified Data.Map as Map
import Data.Morpheus.Server (deriveApp, runApp) import Data.Morpheus.Server (deriveApp, runApp)
import Data.Morpheus.Server.Types (defaultRootResolver, RootResolver(..), Undefined) import Data.Morpheus.Server.Types (defaultRootResolver, RootResolver(..), Undefined)
import Data.Morpheus.Types (Arg(..), GQLType, GQLError, App) import Data.Morpheus.Types (Arg(..), GQLType, GQLError, App)
@ -108,7 +109,7 @@ newTokenArgsToData :: (MonadRandom m, MonadTime m, MonadPermissions m) =>
newTokenArgsToData NewTokenArgs {..} user = do newTokenArgsToData NewTokenArgs {..} user = do
tokenData <- B64.encodeBase64 <$> getRandomBytes 128 tokenData <- B64.encodeBase64 <$> getRandomBytes 128
issued <- currentTime issued <- currentTime
permissions <- maybe currentPermissions pure =<< maybe (pure Nothing) toPermissions permissions permissions <- maybe currentPermissions pure $ inputPermissionsToPermissions <$> permissions
let expires = Nothing let expires = Nothing
pure NewTokenData {..} pure NewTokenData {..}
@ -155,7 +156,7 @@ resolveQuery = Query
, user = \(Arg id) -> targetUser id >>= \user -> requirePermission (Profile user) ReadOnly >> , user = \(Arg id) -> targetUser id >>= \user -> requirePermission (Profile user) ReadOnly >>
(Just . dbUserToUser <$> (dbGetUser user >>= liftDBEither)) `catchError` const (pure Nothing) (Just . dbUserToUser <$> (dbGetUser user >>= liftDBEither)) `catchError` const (pure Nothing)
, applications = requirePermission Applications ReadOnly >> map dbUserToUser <$> dbGetApplications , applications = requirePermission Applications ReadOnly >> map dbUserToUser <$> dbGetApplications
, permissions = currentPermissions , permissions = fromPermissions <$> currentPermissions
} }
resolveMutation :: (MonadRequest m, MonadEmail m, MonadRandom m, MonadTime m, resolveMutation :: (MonadRequest m, MonadEmail m, MonadRandom m, MonadTime m,
@ -209,7 +210,7 @@ data NewKeyArgs = NewKeyArgs { comment :: Maybe Text, keyData :: Base64, expires
deriving (Generic, GQLType, Eq, Show) deriving (Generic, GQLType, Eq, Show)
data NewTokenArgs = NewTokenArgs data NewTokenArgs = NewTokenArgs
{ comment :: Maybe Text, name :: Maybe Text, permissions :: Maybe Text } { comment :: Maybe Text, name :: Maybe Text, permissions :: Maybe [InputScopePermission] }
deriving (Generic, GQLType) deriving (Generic, GQLType)
data User m = User data User m = User
@ -223,7 +224,7 @@ data User m = User
, homeplace :: m Text , homeplace :: m Text
, registered :: m Time , registered :: m Time
, accepted :: m (Maybe Time) , accepted :: m (Maybe Time)
, permissions :: m Text , permissions :: m [ScopePermission]
, isMember :: m Bool , isMember :: m Bool
, application :: m Text , application :: m Text
, tokens :: m [Token m] , tokens :: m [Token m]
@ -246,14 +247,14 @@ data Token m = Token
, comment :: m Text , comment :: m Text
, issued :: m Time , issued :: m Time
, expires :: m (Maybe Time) , expires :: m (Maybe Time)
, permissions :: m Text , permissions :: m [ScopePermission]
} deriving (Generic, GQLType) } deriving (Generic, GQLType)
data Query m = Query data Query m = Query
{ users :: m [User m] { users :: m [User m]
, user :: Arg "id" (Maybe UserID) -> m (Maybe (User m)) , user :: Arg "id" (Maybe UserID) -> m (Maybe (User m))
, applications :: m [User m] , applications :: m [User m]
, permissions :: m Text , permissions :: m [ScopePermission]
} deriving (Generic, GQLType) } deriving (Generic, GQLType)
data Mutation m = Mutation data Mutation m = Mutation
@ -279,7 +280,7 @@ dbUserToUser DBUser {..} = User
, homeplace = dbUserHomeplace , homeplace = dbUserHomeplace
, registered = dbUserRegistered , registered = dbUserRegistered
, accepted = dbUserAccepted , accepted = dbUserAccepted
, permissions = dbUserPermissions , permissions = fromPermissions <$> dbUserPermissions
, isMember = isJust <$> dbUserAccepted , isMember = isJust <$> dbUserAccepted
, application = dbUserApplication , application = dbUserApplication
, tokens = dbUserId >>= flip requirePermission ReadOnly . Tokens >> map dbTokenToToken <$> dbUserTokens , tokens = dbUserId >>= flip requirePermission ReadOnly . Tokens >> map dbTokenToToken <$> dbUserTokens
@ -304,5 +305,19 @@ dbTokenToToken DBToken {..} = Token
, comment = dbTokenComment , comment = dbTokenComment
, issued = dbTokenIssued , issued = dbTokenIssued
, expires = dbTokenExpires , expires = dbTokenExpires
, permissions = dbTokenPermissions , permissions = fromPermissions <$> dbTokenPermissions
} }
inputPermissionsToPermissions :: [InputScopePermission] -> Permissions
inputPermissionsToPermissions = Permissions .
Map.fromList . map (\InputScopePermission {..} -> (scope, permission))
fromPermissions :: Permissions -> [ScopePermission]
fromPermissions (Permissions perms) =
map (\(scope, permission) -> ScopePermission scope permission) $ Map.toList perms
data InputScopePermission = InputScopePermission {scope :: Scope, permission :: Permission}
deriving (Generic, GQLType, Eq, Show)
data ScopePermission = ScopePermission {scope :: Scope, permission :: Permission}
deriving (Generic, GQLType)

View File

@ -6,6 +6,7 @@
module Datarekisteri.Backend.Sql.Queries where module Datarekisteri.Backend.Sql.Queries where
import Control.Monad (void)
import Control.Monad.Logger (LoggingT) import Control.Monad.Logger (LoggingT)
import Data.Aeson (fromJSON, toJSON, Result(..)) import Data.Aeson (fromJSON, toJSON, Result(..))
import Data.Maybe (listToMaybe) import Data.Maybe (listToMaybe)
@ -46,11 +47,11 @@ getKeys user = select $ do
getKey :: KeyID -> SqlM (Maybe SqlKey) getKey :: KeyID -> SqlM (Maybe SqlKey)
getKey = get . fromID getKey = get . fromID
getPermissions :: UserID -> SqlM (Maybe Text) getPermissions :: UserID -> SqlM (Maybe Permissions)
getPermissions user = fmap (fmap sqlUserPermissions) $ get $ fromID user getPermissions user = fmap (fmap sqlUserPermissions) $ get $ fromID user
setPermissions :: UserID -> Text -> SqlM () setPermissions :: UserID -> Permissions -> SqlM ()
setPermissions user txt = updateUserData user [SqlUserPermissions Persist.=. txt] [] >> return () setPermissions user permissions = void $ updateUserData user [SqlUserPermissions Persist.=. permissions] []
getPrimaryKey :: UserID -> SqlM (Maybe (Entity SqlKey)) getPrimaryKey :: UserID -> SqlM (Maybe (Entity SqlKey))
getPrimaryKey user = fmap listToMaybe $ select $ do getPrimaryKey user = fmap listToMaybe $ select $ do

View File

@ -28,7 +28,7 @@ mkPersist sqlSettings [persistUpperCase|
SqlUser sql=users SqlUser sql=users
registered Time registered Time
passwordCrypt PasswordHash passwordCrypt PasswordHash
permissions Text permissions Permissions
accepted (Maybe Time) accepted (Maybe Time)
memberData Value sqltype=jsonb memberData Value sqltype=jsonb
@ -68,7 +68,7 @@ SqlToken sql=tokens
comment Text comment Text
issued Time issued Time
expires (Maybe Time) expires (Maybe Time)
permissions Text permissions Permissions
UniqueNameUid name uid UniqueNameUid name uid
UniqueData data UniqueData data

View File

@ -36,7 +36,7 @@ forward :: Monad m => [a] -> m [Maybe a]
forward = pure . map Just forward = pure . map Just
requirePermission :: (MonadPermissions m, MonadError GQLError m) => Scope -> Permission -> m () requirePermission :: (MonadPermissions m, MonadError GQLError m) => Scope -> Permission -> m ()
requirePermission scope permission = unlessM (hasPermission scope permission) $ requirePermission scope permission = unlessM (hasPermission scope permission <$> currentPermissions) $
throwError $ "Insufficient permissions, " <> show permission <> " for " throwError $ "Insufficient permissions, " <> show permission <> " for "
<> show scope <> " required." <> show scope <> " required."
@ -64,7 +64,7 @@ data ApplicationData = ApplicationData
, registered :: Time , registered :: Time
, verificationSecret :: Text , verificationSecret :: Text
, passwordHash :: PasswordHash , passwordHash :: PasswordHash
, permissions :: Text , permissions :: Permissions
} deriving (Generic, Eq, Show) } deriving (Generic, Eq, Show)
data UpdateData = UpdateData data UpdateData = UpdateData
@ -99,7 +99,7 @@ data Page a m = Page { pageData :: m a, cursor :: m (Maybe Cursor) }
data NewTokenData = NewTokenData data NewTokenData = NewTokenData
{ comment :: Maybe Text { comment :: Maybe Text
, name :: Maybe Text , name :: Maybe Text
, permissions :: Text , permissions :: Permissions
, tokenData :: Text , tokenData :: Text
, issued :: Time , issued :: Time
, expires :: Maybe Time , expires :: Maybe Time
@ -122,7 +122,7 @@ data DBUser m = DBUser
, dbUserHomeplace :: m Text , dbUserHomeplace :: m Text
, dbUserRegistered :: m Time , dbUserRegistered :: m Time
, dbUserAccepted :: m (Maybe Time) , dbUserAccepted :: m (Maybe Time)
, dbUserPermissions :: m Text , dbUserPermissions :: m Permissions
, dbUserApplication :: m Text , dbUserApplication :: m Text
, dbUserPasswordHash :: m PasswordHash , dbUserPasswordHash :: m PasswordHash
, dbUserTokens :: m [DBToken m] , dbUserTokens :: m [DBToken m]
@ -146,7 +146,7 @@ data DBToken m = DBToken
, dbTokenComment :: m Text , dbTokenComment :: m Text
, dbTokenIssued :: m Time , dbTokenIssued :: m Time
, dbTokenExpires :: m (Maybe Time) , dbTokenExpires :: m (Maybe Time)
, dbTokenPermissions :: m Text , dbTokenPermissions :: m Permissions
} }
class Monad m => MonadTime m where class Monad m => MonadTime m where
@ -180,10 +180,8 @@ class Monad m => MonadRequest m where
currentUser :: m (Maybe UserID) currentUser :: m (Maybe UserID)
class Monad m => MonadPermissions m where class Monad m => MonadPermissions m where
hasPermission :: Scope -> Permission -> m Bool currentPermissions :: m Permissions
currentPermissions :: m Text defaultPermissions :: m Permissions
defaultPermissions :: m Text
toPermissions :: Text -> m (Maybe Text)
instance (MonadDB m, LiftOperation o) => MonadDB (Resolver o () m) where instance (MonadDB m, LiftOperation o) => MonadDB (Resolver o () m) where
dbUpdateUser = fmap (fmap liftUser) . lift . dbUpdateUser dbUpdateUser = fmap (fmap liftUser) . lift . dbUpdateUser
@ -204,10 +202,8 @@ instance (MonadRequest m, LiftOperation o) => MonadRequest (Resolver o () m) whe
currentUser = lift currentUser currentUser = lift currentUser
instance (MonadPermissions m, LiftOperation o) => MonadPermissions (Resolver o () m) where instance (MonadPermissions m, LiftOperation o) => MonadPermissions (Resolver o () m) where
hasPermission scope permission = lift $ hasPermission scope permission
defaultPermissions = lift defaultPermissions defaultPermissions = lift defaultPermissions
currentPermissions = lift currentPermissions currentPermissions = lift currentPermissions
toPermissions = lift . toPermissions
instance (MonadEmail m, LiftOperation o) => MonadEmail (Resolver o () m) where instance (MonadEmail m, LiftOperation o) => MonadEmail (Resolver o () m) where
sendEmail = lift . sendEmail sendEmail = lift . sendEmail

View File

@ -43,7 +43,7 @@ addUserMain AddUserOpts {..} = do
userID <- Sql.addUser $ SqlUser userID <- Sql.addUser $ SqlUser
{ sqlUserRegistered = time { sqlUserRegistered = time
, sqlUserPasswordCrypt = passwordHash , sqlUserPasswordCrypt = passwordHash
, sqlUserPermissions = show addUserPermissions , sqlUserPermissions = addUserPermissions
, sqlUserAccepted = Just time , sqlUserAccepted = Just time
, sqlUserMemberData = toJSON $ MemberData , sqlUserMemberData = toJSON $ MemberData
{ nickname = addUserNickname { nickname = addUserNickname
@ -82,7 +82,7 @@ updateUserMain UpdateUserOpts {..} = runQuery $ do
Just application -> Sql.updateUserData updateUserId [] [Sql.SetUserApplication application] Just application -> Sql.updateUserData updateUserId [] [Sql.SetUserApplication application]
case updateUserPermissions of case updateUserPermissions of
Nothing -> pure () Nothing -> pure ()
Just permissions -> Sql.setPermissions updateUserId $ show permissions Just permissions -> Sql.setPermissions updateUserId permissions
cliOptions :: Parser CLIOptions cliOptions :: Parser CLIOptions
cliOptions = CLIOptions cliOptions = CLIOptions
@ -114,8 +114,8 @@ updateUserCommand = fmap UpdateUser $ UpdateUserOpts
<*> optional (strOption (long "application" <> metavar "TEXT")) <*> optional (strOption (long "application" <> metavar "TEXT"))
<*> optional permissionsParser <*> optional permissionsParser
permissionsParser :: Parser (Map Scope Permission) permissionsParser :: Parser Permissions
permissionsParser = fromList <$> many permissionParser permissionsParser = Permissions . fromList <$> many permissionParser
permissionParser :: Parser (Scope, Permission) permissionParser :: Parser (Scope, Permission)
permissionParser = (,) permissionParser = (,)
@ -136,7 +136,7 @@ data CLISubCommand = AddUser AddUserOpts
data UpdateUserOpts = UpdateUserOpts data UpdateUserOpts = UpdateUserOpts
{ updateUserId :: UserID { updateUserId :: UserID
, updateUserApplication :: Maybe Text , updateUserApplication :: Maybe Text
, updateUserPermissions :: Maybe (Map Scope Permission) , updateUserPermissions :: Maybe Permissions
} }
data AddUserOpts = AddUserOpts data AddUserOpts = AddUserOpts
@ -146,7 +146,7 @@ data AddUserOpts = AddUserOpts
, addUserHomeplace :: Text , addUserHomeplace :: Text
, addUserPhoneNumber :: PhoneNumber , addUserPhoneNumber :: PhoneNumber
, addUserEmail :: Email , addUserEmail :: Email
, addUserPermissions :: Map Scope Permission , addUserPermissions :: Permissions
, addUserApplication :: Text , addUserApplication :: Text
} }

View File

@ -22,7 +22,7 @@ module Datarekisteri.Core.Types
, addTime , addTime
, base64Decode , base64Decode
, base64Encode , base64Encode
, readPermission , hasPermission
, renderDate , renderDate
, renderEmail , renderEmail
, renderPhoneNumber , renderPhoneNumber
@ -40,6 +40,7 @@ import qualified "base64" Data.ByteString.Base64 as B64
import Data.Aeson (Value, ToJSON(..), FromJSON(..), Result(..), ToJSONKey(..), FromJSONKey(..), FromJSONKeyFunction(..), ToJSONKeyFunction(..), fromJSON, eitherDecodeStrict) import Data.Aeson (Value, ToJSON(..), FromJSON(..), Result(..), ToJSONKey(..), FromJSONKey(..), FromJSONKeyFunction(..), ToJSONKeyFunction(..), fromJSON, eitherDecodeStrict)
import Data.Aeson.Encoding (string) import Data.Aeson.Encoding (string)
import Data.Char (isSpace) import Data.Char (isSpace)
import qualified Data.Map as Map
import Data.Morpheus.Server.Types (SCALAR, TYPE) import Data.Morpheus.Server.Types (SCALAR, TYPE)
import Data.Morpheus.Types (GQLType, DecodeScalar(..), KIND, EncodeScalar(..), ScalarValue(..)) import Data.Morpheus.Types (GQLType, DecodeScalar(..), KIND, EncodeScalar(..), ScalarValue(..))
import Data.Morpheus.Types.GQLScalar (scalarToJSON, scalarFromJSON) import Data.Morpheus.Types.GQLScalar (scalarToJSON, scalarFromJSON)
@ -120,6 +121,10 @@ instance GQLType Permission where type KIND Permission = SCALAR
instance ToJSON Permission where toJSON = scalarToJSON instance ToJSON Permission where toJSON = scalarToJSON
instance FromJSON Permission where parseJSON = scalarFromJSON <=< parseJSON instance FromJSON Permission where parseJSON = scalarFromJSON <=< parseJSON
hasPermission :: Scope -> Permission -> Permissions -> Bool
hasPermission scope permission (Permissions permissionMap) =
Map.findWithDefault None scope permissionMap >= permission
newtype Permissions = Permissions (Map Scope Permission) newtype Permissions = Permissions (Map Scope Permission)
deriving (Show, Eq, Read, Generic, ToJSON, FromJSON, Semigroup, Monoid) deriving (Show, Eq, Read, Generic, ToJSON, FromJSON, Semigroup, Monoid)
@ -137,9 +142,6 @@ instance Ord Permissions where
(Permissions a) <= (Permissions b) = (Permissions a) <= (Permissions b) =
and $ Map.intersectionWith (<=) a $ b <> (const None <$> Map.difference a b) and $ Map.intersectionWith (<=) a $ b <> (const None <$> Map.difference a b)
readPermission :: Text -> Maybe Permissions
readPermission = rightToMaybe . readEither . toString
newtype PhoneNumber = PhoneNumber Text deriving (Show, Generic) newtype PhoneNumber = PhoneNumber Text deriving (Show, Generic)
renderPhoneNumber :: PhoneNumber -> Text renderPhoneNumber :: PhoneNumber -> Text

View File

@ -6,8 +6,12 @@ scalar Email
scalar KeyID scalar KeyID
scalar Permission
scalar PhoneNumber scalar PhoneNumber
scalar Scope
scalar Time scalar Time
scalar TokenID scalar TokenID
@ -19,6 +23,11 @@ enum Unit {
Unit2 Unit2
} }
input InputScopePermission {
scope: Scope!
permission: Permission!
}
type PGPKey { type PGPKey {
id: KeyID! id: KeyID!
pgpKeyData: Base64! pgpKeyData: Base64!
@ -27,6 +36,11 @@ type PGPKey {
comment: String! comment: String!
} }
type ScopePermission {
scope: Scope!
permission: Permission!
}
type Token { type Token {
id: TokenID! id: TokenID!
name: String name: String
@ -34,7 +48,7 @@ type Token {
comment: String! comment: String!
issued: Time! issued: Time!
expires: Time expires: Time
permissions: String! permissions: [ScopePermission!]!
} }
type User { type User {
@ -48,7 +62,7 @@ type User {
homeplace: String! homeplace: String!
registered: Time! registered: Time!
accepted: Time accepted: Time
permissions: String! permissions: [ScopePermission!]!
isMember: Boolean! isMember: Boolean!
application: String! application: String!
tokens: [Token!]! tokens: [Token!]!
@ -60,7 +74,7 @@ type Query {
users: [User!]! users: [User!]!
user(id: UserID): User user(id: UserID): User
applications: [User!]! applications: [User!]!
permissions: String! permissions: [ScopePermission!]!
} }
type Mutation { type Mutation {
@ -68,7 +82,7 @@ type Mutation {
verifyEmail(secret: String!): Boolean! verifyEmail(secret: String!): Boolean!
resendVerificationEmail(user: UserID): Unit! resendVerificationEmail(user: UserID): Unit!
update(email: Email, phoneNumber: PhoneNumber, password: String, name: String, nickname: String, homeplace: String, application: String, user: UserID): User! update(email: Email, phoneNumber: PhoneNumber, password: String, name: String, nickname: String, homeplace: String, application: String, user: UserID): User!
newToken(comment: String, name: String, permissions: String): Token! newToken(comment: String, name: String, permissions: [InputScopePermission!]): Token!
newKey(comment: String, keyData: Base64!, expires: Time): PGPKey! newKey(comment: String, keyData: Base64!, expires: Time): PGPKey!
accept(user: UserID!): Unit! accept(user: UserID!): Unit!
reject(user: UserID!): Unit! reject(user: UserID!): Unit!

View File

@ -29,10 +29,12 @@ import Yesod
import Yesod.Auth import Yesod.Auth
import Yesod.Static import Yesod.Static
import Datarekisteri.Core.Types (UserID(..), Scope(..), Permission(..), readPermission) import Datarekisteri.Core.Types (UserID(..), Scope(..), Permission(..), Permissions(..))
import Datarekisteri.Frontend.ApiRequests import Datarekisteri.Frontend.ApiRequests
import Datarekisteri.Frontend.Auth import Datarekisteri.Frontend.Auth
import qualified Datarekisteri.Core.Types as Core
data DataIdClient = DataIdClient data DataIdClient = DataIdClient
{ getStatic :: Static { getStatic :: Static
, getConfig :: Config , getConfig :: Config
@ -70,7 +72,10 @@ mkYesodData "DataIdClient" [parseRoutes|
declareLocalTypesInline "schema.gql" [raw| declareLocalTypesInline "schema.gql" [raw|
query GetPermissions { query GetPermissions {
permissions permissions {
scope
permission
}
user { user {
id id
} }
@ -98,18 +103,20 @@ instance YesodAuth DataIdClient where
withAuthenticated :: (AuthId DataIdClient -> Handler AuthResult) -> Handler AuthResult withAuthenticated :: (AuthId DataIdClient -> Handler AuthResult) -> Handler AuthResult
withAuthenticated m = maybeAuthId >>= maybe (pure AuthenticationRequired) m withAuthenticated m = maybeAuthId >>= maybe (pure AuthenticationRequired) m
readPermissions :: [GetPermissionsPermissions] -> Permissions
readPermissions = Permissions . fromList . map (\GetPermissionsPermissions {..} -> (scope, permission))
hasPermission :: Scope -> Permission -> Handler Bool hasPermission :: Scope -> Permission -> Handler Bool
hasPermission scope permission = do hasPermission scope permission = do
GetPermissions {..} <- apiRequest @GetPermissions False () GetPermissions {..} <- apiRequest @GetPermissions False ()
let permissionMap = fromMaybe mempty $ readPermission permissions let permissions' = readPermissions permissions
findPermission scope = findWithDefault None scope permissionMap
userID = (\x -> id (x :: GetPermissionsUser)) <$> user userID = (\x -> id (x :: GetPermissionsUser)) <$> user
scopes = scope :| case scope of scopes = scope :| case scope of
Tokens uid | Just uid == userID -> [OwnTokens] Tokens uid | Just uid == userID -> [OwnTokens]
Profile uid | Just uid == userID -> [OwnProfile] Profile uid | Just uid == userID -> [OwnProfile]
_ -> [] _ -> []
-- TODO add Members and Applications to Profile Scopes if profile owner is member/applicant -- TODO add Members and Applications to Profile Scopes if profile owner is member/applicant
pure $ (>= permission) $ maximum1 $ findPermission <$> scopes pure $ any (\scope -> Core.hasPermission scope permission permissions') scopes
requirePermission :: Scope -> Permission -> Handler AuthResult requirePermission :: Scope -> Permission -> Handler AuthResult
requirePermission scope permission = ifM (hasPermission scope permission) requirePermission scope permission = ifM (hasPermission scope permission)