diff --git a/config/routes b/config/routes
index a96f90a..72d1949 100644
--- a/config/routes
+++ b/config/routes
@@ -50,17 +50,17 @@
/k/!new KeyNewR GET
/k/#KyIdent KeyR GET DELETE POST
-/rr RepoRolesR GET POST
-/rr/!new RepoRoleNewR GET
-/rr/#RlIdent RepoRoleR GET DELETE POST
-/rr/#RlIdent/a RepoRoleOpsR GET POST
-/rr/#RlIdent/a/!new RepoRoleOpNewR GET
+/s/#ShrIdent/rr RepoRolesR GET POST
+/s/#ShrIdent/rr/!new RepoRoleNewR GET
+/s/#ShrIdent/rr/#RlIdent RepoRoleR GET DELETE POST
+/s/#ShrIdent/rr/#RlIdent/a RepoRoleOpsR GET POST
+/s/#ShrIdent/rr/#RlIdent/a/!new RepoRoleOpNewR GET
-/pr ProjectRolesR GET POST
-/pr/!new ProjectRoleNewR GET
-/pr/#RlIdent ProjectRoleR GET DELETE POST
-/pr/#RlIdent/a ProjectRoleOpsR GET POST
-/pr/#RlIdent/a/!new ProjectRoleOpNewR GET
+/s/#ShrIdent/pr ProjectRolesR GET POST
+/s/#ShrIdent/pr/!new ProjectRoleNewR GET
+/s/#ShrIdent/pr/#RlIdent ProjectRoleR GET DELETE POST
+/s/#ShrIdent/pr/#RlIdent/a ProjectRoleOpsR GET POST
+/s/#ShrIdent/pr/#RlIdent/a/!new ProjectRoleOpNewR GET
-- ----------------------------------------------------------------------------
-- Projects
diff --git a/src/Vervis/Foundation.hs b/src/Vervis/Foundation.hs
index fc815cf..1a36078 100644
--- a/src/Vervis/Foundation.hs
+++ b/src/Vervis/Foundation.hs
@@ -120,57 +120,57 @@ instance Yesod App where
-- Who can access which pages.
isAuthorized r w = case (r, w) of
- (GroupsR , True) -> personAny
- (GroupNewR , _ ) -> personAny
- (GroupMembersR grp , True) -> groupRole (== GRAdmin) grp
- (GroupMemberNewR grp , _ ) -> groupRole (== GRAdmin) grp
- (GroupMemberR grp _memb , True) -> groupRole (== GRAdmin) grp
+ (GroupsR , True) -> personAny
+ (GroupNewR , _ ) -> personAny
+ (GroupMembersR grp , True) -> groupAdmin grp
+ (GroupMemberNewR grp , _ ) -> groupAdmin grp
+ (GroupMemberR grp _memb , True) -> groupAdmin grp
- (KeysR , _ ) -> personAny
- (KeyR _key , _ ) -> personAny
- (KeyNewR , _ ) -> personAny
+ (KeysR , _ ) -> personAny
+ (KeyR _key , _ ) -> personAny
+ (KeyNewR , _ ) -> personAny
- (RepoRolesR , _ ) -> personAny
- (RepoRoleNewR , _ ) -> personAny
- (RepoRoleR _rl , _ ) -> personAny
- (RepoRoleOpsR _rl , _ ) -> personAny
- (RepoRoleOpNewR _rl , _ ) -> personAny
+ (RepoRolesR shr , _ ) -> personOrGroupAdmin shr
+ (RepoRoleNewR shr , _ ) -> personOrGroupAdmin shr
+ (RepoRoleR shr _rl , _ ) -> personOrGroupAdmin shr
+ (RepoRoleOpsR shr _rl , _ ) -> personOrGroupAdmin shr
+ (RepoRoleOpNewR shr _rl , _ ) -> personOrGroupAdmin shr
- (ProjectRolesR , _ ) -> personAny
- (ProjectRoleNewR , _ ) -> personAny
- (ProjectRoleR _rl , _ ) -> personAny
- (ProjectRoleOpsR _rl , _ ) -> personAny
- (ProjectRoleOpNewR _rl , _ ) -> personAny
+ (ProjectRolesR shr , _ ) -> personOrGroupAdmin shr
+ (ProjectRoleNewR shr , _ ) -> personOrGroupAdmin shr
+ (ProjectRoleR shr _rl , _ ) -> personOrGroupAdmin shr
+ (ProjectRoleOpsR shr _rl , _ ) -> personOrGroupAdmin shr
+ (ProjectRoleOpNewR shr _rl , _ ) -> personOrGroupAdmin shr
- (ReposR shar , True) -> person shar
- (RepoNewR user , _ ) -> person user
- (RepoR shar _ , True) -> person shar
- (RepoEditR shr _rp , _ ) -> person shr
- (RepoDevsR shr _rp , _ ) -> person shr
- (RepoDevNewR shr _rp , _ ) -> person shr
- (RepoDevR shr _rp _dev , _ ) -> person shr
+ (ReposR shar , True) -> person shar
+ (RepoNewR user , _ ) -> person user
+ (RepoR shar _ , True) -> person shar
+ (RepoEditR shr _rp , _ ) -> person shr
+ (RepoDevsR shr _rp , _ ) -> person shr
+ (RepoDevNewR shr _rp , _ ) -> person shr
+ (RepoDevR shr _rp _dev , _ ) -> person shr
- (ProjectsR shar , True) -> person shar
- (ProjectNewR user , _ ) -> person user
- (ProjectR shr _prj , True) -> person shr
- (ProjectEditR shr _prj , _ ) -> person shr
+ (ProjectsR shar , True) -> person shar
+ (ProjectNewR user , _ ) -> person user
+ (ProjectR shr _prj , True) -> person shr
+ (ProjectEditR shr _prj , _ ) -> person shr
(ProjectDevsR shr _prj , _ ) -> person shr
(ProjectDevNewR shr _prj , _ ) -> person shr
(ProjectDevR shr _prj _dev , _ ) -> person shr
- (TicketsR shar _ , True) -> person shar
- (TicketNewR _ _ , _ ) -> personAny
- (TicketR user _ _ , True) -> person user
- (TicketEditR user _ _ , _ ) -> person user
- (TicketCloseR user _ _ , _ ) -> person user
- (TicketOpenR user _ _ , _ ) -> person user
- (TicketClaimR s j _ , _ ) -> projOp ProjOpClaimTicket s j
- (TicketUnclaimR s j _ , _ ) -> projOp ProjOpUnclaimTicket s j
- (TicketDiscussionR _ _ _ , True) -> personAny
- (TicketMessageR _ _ _ _ , True) -> personAny
- (TicketTopReplyR _ _ _ , _ ) -> personAny
- (TicketReplyR _ _ _ _ , _ ) -> personAny
- _ -> return Authorized
+ (TicketsR shar _ , True) -> person shar
+ (TicketNewR _ _ , _ ) -> personAny
+ (TicketR user _ _ , True) -> person user
+ (TicketEditR user _ _ , _ ) -> person user
+ (TicketCloseR user _ _ , _ ) -> person user
+ (TicketOpenR user _ _ , _ ) -> person user
+ (TicketClaimR s j _ , _ ) -> projOp ProjOpClaimTicket s j
+ (TicketUnclaimR s j _ , _ ) -> projOp ProjOpUnclaimTicket s j
+ (TicketDiscussionR _ _ _ , True) -> personAny
+ (TicketMessageR _ _ _ _ , True) -> personAny
+ (TicketTopReplyR _ _ _ , _ ) -> personAny
+ (TicketReplyR _ _ _ _ , _ ) -> personAny
+ _ -> return Authorized
where
personAnd
:: (Entity Person -> Handler AuthResult) -> Handler AuthResult
@@ -205,6 +205,43 @@ instance Yesod App where
then Authorized
else Unauthorized "Not the expected group role"
+ groupAdmin :: ShrIdent -> Handler AuthResult
+ groupAdmin = groupRole (== GRAdmin)
+
+ personOrGroupAdmin :: ShrIdent -> Handler AuthResult
+ personOrGroupAdmin shr = personAnd $ \ (Entity vpid _vp) -> runDB $ do
+ mes <- getBy $ UniqueSharer shr
+ case mes of
+ Nothing -> return $ Unauthorized "No such sharer"
+ Just (Entity sid _) -> do
+ mep <- getBy $ UniquePersonIdent sid
+ case mep of
+ Just (Entity pid _p) ->
+ return $ if pid == vpid
+ then Authorized
+ else
+ Unauthorized
+ "Can’t access other people’s roles"
+ Nothing -> do
+ meg <- getBy $ UniqueGroup sid
+ case meg of
+ Nothing -> do
+ $logWarn $
+ "Found non-person non-group \
+ \sharer: " <> shr2text shr
+ return $ Unauthorized "Zombine sharer"
+ Just (Entity gid _g) -> do
+ mem <- getBy $ UniqueGroupMember vpid gid
+ return $ case mem of
+ Nothing ->
+ Unauthorized "Not a group member"
+ Just (Entity _mid m) ->
+ if groupMemberRole m == GRAdmin
+ then Authorized
+ else
+ Unauthorized
+ "Not a group admin"
+
projOp
:: ProjectOperation -> ShrIdent -> PrjIdent -> Handler AuthResult
projOp op shr prj = personAnd $ \ (Entity pid _p) -> do
@@ -333,17 +370,27 @@ instance YesodBreadcrumbs App where
KeyNewR -> ("New", Just KeysR)
KeyR key -> (ky2text key, Just KeysR)
- RepoRolesR -> ("Repo Roles", Just HomeR)
- RepoRoleNewR -> ("New", Just RepoRolesR)
- RepoRoleR rl -> (rl2text rl, Just RepoRolesR)
- RepoRoleOpsR rl -> ("Operations", Just $ RepoRoleR rl)
- RepoRoleOpNewR rl -> ("New", Just $ RepoRoleOpsR rl)
+ RepoRolesR shr -> ("Repo Roles", Just $ SharerR shr)
+ RepoRoleNewR shr -> ("New", Just $ RepoRolesR shr)
+ RepoRoleR shr rl -> (rl2text rl, Just $ RepoRolesR shr)
+ RepoRoleOpsR shr rl -> ( "Operations"
+ , Just $ RepoRoleR shr rl
+ )
+ RepoRoleOpNewR shr rl -> ("New", Just $ RepoRoleOpsR shr rl)
- ProjectRolesR -> ("Project Roles", Just HomeR)
- ProjectRoleNewR -> ("New", Just ProjectRolesR)
- ProjectRoleR rl -> (rl2text rl, Just ProjectRolesR)
- ProjectRoleOpsR rl -> ("Operations", Just $ ProjectRoleR rl)
- ProjectRoleOpNewR rl -> ("New", Just $ ProjectRoleOpsR rl)
+ ProjectRolesR shr -> ( "Project Roles"
+ , Just $ SharerR shr
+ )
+ ProjectRoleNewR shr -> ("New", Just $ ProjectRolesR shr)
+ ProjectRoleR shr rl -> ( rl2text rl
+ , Just $ ProjectRolesR shr
+ )
+ ProjectRoleOpsR shr rl -> ( "Operations"
+ , Just $ ProjectRoleR shr rl
+ )
+ ProjectRoleOpNewR shr rl -> ( "New"
+ , Just $ ProjectRoleOpsR shr rl
+ )
ReposR shar -> ("Repos", Just $ PersonR shar)
RepoNewR shar -> ("New", Just $ ReposR shar)
diff --git a/src/Vervis/Handler/Role.hs b/src/Vervis/Handler/Role.hs
index 9775bd0..d0ea50d 100644
--- a/src/Vervis/Handler/Role.hs
+++ b/src/Vervis/Handler/Role.hs
@@ -49,21 +49,19 @@ import Yesod.Persist.Core (runDB, getBy404)
import Vervis.Form.Role
import Vervis.Foundation
import Vervis.Model
-import Vervis.Model.Ident (RlIdent, rl2text)
+import Vervis.Model.Ident (ShrIdent, RlIdent, rl2text)
import Vervis.Settings (widgetFile)
-getRepoRolesR :: Handler Html
-getRepoRolesR = do
- pid <- requireAuthId
+getRepoRolesR :: ShrIdent -> Handler Html
+getRepoRolesR shr = do
roles <- runDB $ do
- person <- getJust pid
- selectList [RepoRoleSharer ==. personIdent person] []
+ Entity sid _ <- getBy404 $ UniqueSharer shr
+ selectList [RepoRoleSharer ==. sid] []
defaultLayout $(widgetFile "repo/role/list")
-postRepoRolesR :: Handler Html
-postRepoRolesR = do
- pid <- requireAuthId
- sid <- fmap personIdent $ runDB $ getJust pid
+postRepoRolesR :: ShrIdent -> Handler Html
+postRepoRolesR shr = do
+ sid <- fmap entityKey $ runDB $ getBy404 $ UniqueSharer shr
((result, widget), enctype) <- runFormPost $ newRepoRoleForm sid
case result of
FormSuccess nrr -> do
@@ -74,7 +72,7 @@ postRepoRolesR = do
, repoRoleDesc = nrrDesc nrr
}
insert_ role
- redirect $ RepoRolesR
+ redirect $ RepoRolesR shr
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "repo/role/new")
@@ -82,53 +80,48 @@ postRepoRolesR = do
setMessage "Invalid input, see errors below"
defaultLayout $(widgetFile "repo/role/new")
-getRepoRoleNewR :: Handler Html
-getRepoRoleNewR = do
- pid <- requireAuthId
- sid <- fmap personIdent $ runDB $ getJust pid
+getRepoRoleNewR :: ShrIdent -> Handler Html
+getRepoRoleNewR shr = do
+ sid <- fmap entityKey $ runDB $ getBy404 $ UniqueSharer shr
((_result, widget), enctype) <- runFormPost $ newRepoRoleForm sid
defaultLayout $(widgetFile "repo/role/new")
-getRepoRoleR :: RlIdent -> Handler Html
-getRepoRoleR rl = do
- pid <- requireAuthId
- sid <- fmap personIdent $ runDB $ getJust pid
- Entity _rid role <- runDB $ getBy404 $ UniqueRepoRole sid rl
+getRepoRoleR :: ShrIdent -> RlIdent -> Handler Html
+getRepoRoleR shr rl = do
+ Entity _rid role <- runDB $ do
+ Entity sid _ <- getBy404 $ UniqueSharer shr
+ getBy404 $ UniqueRepoRole sid rl
defaultLayout $(widgetFile "repo/role/one")
-deleteRepoRoleR :: RlIdent -> Handler Html
-deleteRepoRoleR rl = do
- pid <- requireAuthId
+deleteRepoRoleR :: ShrIdent -> RlIdent -> Handler Html
+deleteRepoRoleR shr rl = do
runDB $ do
- person <- getJust pid
- let sid = personIdent person
+ Entity sid _s <- getBy404 $ UniqueSharer shr
Entity rid _r <- getBy404 $ UniqueRepoRole sid rl
delete rid
setMessage "Role deleted."
- redirect RepoRolesR
+ redirect $ RepoRolesR shr
-postRepoRoleR :: RlIdent -> Handler Html
-postRepoRoleR rl = do
+postRepoRoleR :: ShrIdent -> RlIdent -> Handler Html
+postRepoRoleR shr rl = do
mmethod <- lookupPostParam "_method"
case mmethod of
- Just "DELETE" -> deleteRepoRoleR rl
+ Just "DELETE" -> deleteRepoRoleR shr rl
_ -> notFound
-getRepoRoleOpsR :: RlIdent -> Handler Html
-getRepoRoleOpsR rl = do
- pid <- requireAuthId
+getRepoRoleOpsR :: ShrIdent -> RlIdent -> Handler Html
+getRepoRoleOpsR shr rl = do
ops <- runDB $ do
- sid <- personIdent <$> getJust pid
+ Entity sid _s <- getBy404 $ UniqueSharer shr
Entity rid _r <- getBy404 $ UniqueRepoRole sid rl
as <- selectList [RepoAccessRole ==. rid] []
return $ map (repoAccessOp . entityVal) as
defaultLayout $(widgetFile "repo/role/op/list")
-postRepoRoleOpsR :: RlIdent -> Handler Html
-postRepoRoleOpsR rl = do
- pid <- requireAuthId
+postRepoRoleOpsR :: ShrIdent -> RlIdent -> Handler Html
+postRepoRoleOpsR shr rl = do
let getrid = do
- sid <- personIdent <$> getJust pid
+ Entity sid _ <- getBy404 $ UniqueSharer shr
fmap entityKey $ getBy404 $ UniqueRepoRole sid rl
((result, widget), enctype) <- runFormPost $ newRepoRoleOpForm getrid
case result of
@@ -140,7 +133,7 @@ postRepoRoleOpsR rl = do
, repoAccessOp = op
}
insert_ access
- redirect $ RepoRoleOpsR rl
+ redirect $ RepoRoleOpsR shr rl
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "repo/role/op/new")
@@ -148,27 +141,24 @@ postRepoRoleOpsR rl = do
setMessage "Invalid input, see errors below"
defaultLayout $(widgetFile "repo/role/op/new")
-getRepoRoleOpNewR :: RlIdent -> Handler Html
-getRepoRoleOpNewR rl = do
- pid <- requireAuthId
+getRepoRoleOpNewR :: ShrIdent -> RlIdent -> Handler Html
+getRepoRoleOpNewR shr rl = do
let getrid = do
- sid <- personIdent <$> getJust pid
+ Entity sid _ <- getBy404 $ UniqueSharer shr
fmap entityKey $ getBy404 $ UniqueRepoRole sid rl
((_result, widget), enctype) <- runFormPost $ newRepoRoleOpForm getrid
defaultLayout $(widgetFile "repo/role/op/new")
-getProjectRolesR :: Handler Html
-getProjectRolesR = do
- pid <- requireAuthId
+getProjectRolesR :: ShrIdent -> Handler Html
+getProjectRolesR shr = do
roles <- runDB $ do
- person <- getJust pid
- selectList [ProjectRoleSharer ==. personIdent person] []
+ Entity sid _ <- getBy404 $ UniqueSharer shr
+ selectList [ProjectRoleSharer ==. sid] []
defaultLayout $(widgetFile "project/role/list")
-postProjectRolesR :: Handler Html
-postProjectRolesR = do
- pid <- requireAuthId
- sid <- fmap personIdent $ runDB $ getJust pid
+postProjectRolesR :: ShrIdent -> Handler Html
+postProjectRolesR shr = do
+ sid <- fmap entityKey $ runDB $ getBy404 $ UniqueSharer shr
((result, widget), enctype) <- runFormPost $ newProjectRoleForm sid
case result of
FormSuccess npr -> do
@@ -179,7 +169,7 @@ postProjectRolesR = do
, projectRoleDesc = nprDesc npr
}
insert_ role
- redirect $ ProjectRolesR
+ redirect $ ProjectRolesR shr
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "project/role/new")
@@ -187,53 +177,48 @@ postProjectRolesR = do
setMessage "Invalid input, see errors below"
defaultLayout $(widgetFile "project/role/new")
-getProjectRoleNewR :: Handler Html
-getProjectRoleNewR = do
- pid <- requireAuthId
- sid <- fmap personIdent $ runDB $ getJust pid
+getProjectRoleNewR :: ShrIdent -> Handler Html
+getProjectRoleNewR shr = do
+ sid <- fmap entityKey $ runDB $ getBy404 $ UniqueSharer shr
((_result, widget), enctype) <- runFormPost $ newProjectRoleForm sid
defaultLayout $(widgetFile "project/role/new")
-getProjectRoleR :: RlIdent -> Handler Html
-getProjectRoleR rl = do
- pid <- requireAuthId
+getProjectRoleR :: ShrIdent -> RlIdent -> Handler Html
+getProjectRoleR shr rl = do
Entity _rid role <- runDB $ do
- sid <- personIdent <$> getJust pid
+ Entity sid _ <- getBy404 $ UniqueSharer shr
getBy404 $ UniqueProjectRole sid rl
defaultLayout $(widgetFile "project/role/one")
-deleteProjectRoleR :: RlIdent -> Handler Html
-deleteProjectRoleR rl = do
- pid <- requireAuthId
+deleteProjectRoleR :: ShrIdent -> RlIdent -> Handler Html
+deleteProjectRoleR shr rl = do
runDB $ do
- sid <- personIdent <$> getJust pid
+ Entity sid _s <- getBy404 $ UniqueSharer shr
Entity rid _r <- getBy404 $ UniqueProjectRole sid rl
delete rid
setMessage "Role deleted."
- redirect ProjectRolesR
+ redirect $ ProjectRolesR shr
-postProjectRoleR :: RlIdent -> Handler Html
-postProjectRoleR rl = do
+postProjectRoleR :: ShrIdent -> RlIdent -> Handler Html
+postProjectRoleR shr rl = do
mmethod <- lookupPostParam "_method"
case mmethod of
- Just "DELETE" -> deleteProjectRoleR rl
+ Just "DELETE" -> deleteProjectRoleR shr rl
_ -> notFound
-getProjectRoleOpsR :: RlIdent -> Handler Html
-getProjectRoleOpsR rl = do
- pid <- requireAuthId
+getProjectRoleOpsR :: ShrIdent -> RlIdent -> Handler Html
+getProjectRoleOpsR shr rl = do
ops <- runDB $ do
- sid <- personIdent <$> getJust pid
+ Entity sid _s <- getBy404 $ UniqueSharer shr
Entity rid _r <- getBy404 $ UniqueProjectRole sid rl
as <- selectList [ProjectAccessRole ==. rid] []
return $ map (projectAccessOp . entityVal) as
defaultLayout $(widgetFile "project/role/op/list")
-postProjectRoleOpsR :: RlIdent -> Handler Html
-postProjectRoleOpsR rl = do
- pid <- requireAuthId
+postProjectRoleOpsR :: ShrIdent -> RlIdent -> Handler Html
+postProjectRoleOpsR shr rl = do
let getrid = do
- sid <- personIdent <$> getJust pid
+ Entity sid _ <- getBy404 $ UniqueSharer shr
fmap entityKey $ getBy404 $ UniqueProjectRole sid rl
((result, widget), enctype) <- runFormPost $ newProjectRoleOpForm getrid
case result of
@@ -245,7 +230,7 @@ postProjectRoleOpsR rl = do
, projectAccessOp = op
}
insert_ access
- redirect $ ProjectRoleOpsR rl
+ redirect $ ProjectRoleOpsR shr rl
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "project/role/op/new")
@@ -253,11 +238,10 @@ postProjectRoleOpsR rl = do
setMessage "Invalid input, see errors below"
defaultLayout $(widgetFile "project/role/op/new")
-getProjectRoleOpNewR :: RlIdent -> Handler Html
-getProjectRoleOpNewR rl = do
- pid <- requireAuthId
+getProjectRoleOpNewR :: ShrIdent -> RlIdent -> Handler Html
+getProjectRoleOpNewR shr rl = do
let getrid = do
- sid <- personIdent <$> getJust pid
+ Entity sid _ <- getBy404 $ UniqueSharer shr
fmap entityKey $ getBy404 $ UniqueProjectRole sid rl
((_result, widget), enctype) <- runFormPost $ newProjectRoleOpForm getrid
defaultLayout $(widgetFile "project/role/op/new")
diff --git a/templates/personal-overview.hamlet b/templates/personal-overview.hamlet
index b4dbad0..59abe74 100644
--- a/templates/personal-overview.hamlet
+++ b/templates/personal-overview.hamlet
@@ -34,9 +34,9 @@ $#
- Repository roles
+ Repository roles
- Project roles
+ Project roles
diff --git a/templates/project/role/list.hamlet b/templates/project/role/list.hamlet
index 01f9cda..43d38cc 100644
--- a/templates/project/role/list.hamlet
+++ b/templates/project/role/list.hamlet
@@ -12,10 +12,11 @@ $# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$#
+ New…
Project Roles
$forall Entity _rid role <- roles