Просмотр исходного кода

Not really an improvemenNot really an improvementt

binwiederhier 3 лет назад
Родитель
Сommit
3d921f4570

+ 4 - 1
server/server_account.go

@@ -316,10 +316,13 @@ func (s *Server) handleAccountAccessAdd(w http.ResponseWriter, r *http.Request,
 	if !topicRegex.MatchString(req.Topic) {
 		return errHTTPBadRequestTopicInvalid
 	}
+	// FIXME authorize: how do I know if v.user (= auth'd user) is allowed to write the ACL entries
+	everyoneRead := util.Contains([]string{"read-write", "rw", "read-only", "read", "ro"}, req.Everyone)
+	everyoneWrite := util.Contains([]string{"read-write", "rw", "write-only", "write", "wo"}, req.Everyone)
 	if err := s.userManager.AllowAccess(v.user.Name, req.Topic, true, true); err != nil {
 		return err
 	}
-	if err := s.userManager.AllowAccess(user.Everyone, req.Topic, false, false); err != nil {
+	if err := s.userManager.AllowAccess(user.Everyone, req.Topic, everyoneRead, everyoneWrite); err != nil {
 		return err
 	}
 	w.Header().Set("Content-Type", "application/json")

+ 2 - 2
server/types.go

@@ -268,6 +268,6 @@ type apiAccountResponse struct {
 }
 
 type apiAccountAccessRequest struct {
-	Topic  string `json:"topic"`
-	Access string `json:"access"`
+	Topic    string `json:"topic"`
+	Everyone string `json:"everyone"`
 }

+ 2 - 1
user/manager.go

@@ -47,7 +47,8 @@ const (
 		);
 		CREATE UNIQUE INDEX idx_user ON user (user);
 		CREATE TABLE IF NOT EXISTS user_access (
-			user_id INT NOT NULL,		
+			user_id INT NOT NULL,
+			owner_user_id INT,			
 			topic TEXT NOT NULL,
 			read INT NOT NULL,
 			write INT NOT NULL,

+ 3 - 41
web/src/components/ActionBar.js

@@ -33,6 +33,7 @@ import Divider from "@mui/material/Divider";
 import {Logout, Person, Settings} from "@mui/icons-material";
 import ListItemIcon from "@mui/material/ListItemIcon";
 import accountApi, {UnauthorizedError} from "../app/AccountApi";
+import PopupMenu from "./PopupMenu";
 
 const ActionBar = (props) => {
     const { t } = useTranslation();
@@ -189,6 +190,7 @@ const SettingsIcons = (props) => {
                 <MoreVertIcon/>
             </IconButton>
             <PopupMenu
+                horizontal="right"
                 anchorEl={anchorEl}
                 open={open}
                 onClose={handleClose}
@@ -259,6 +261,7 @@ const ProfileIcon = () => {
                 </Button>
             }
             <PopupMenu
+                horizontal="right"
                 anchorEl={anchorEl}
                 open={open}
                 onClose={handleClose}
@@ -287,45 +290,4 @@ const ProfileIcon = () => {
     );
 };
 
-const PopupMenu = (props) => {
-    return (
-        <Menu
-            anchorEl={props.anchorEl}
-            open={props.open}
-            onClose={props.onClose}
-            onClick={props.onClose}
-            PaperProps={{
-                elevation: 0,
-                sx: {
-                    overflow: 'visible',
-                    filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
-                    mt: 1.5,
-                    '& .MuiAvatar-root': {
-                        width: 32,
-                        height: 32,
-                        ml: -0.5,
-                        mr: 1,
-                    },
-                    '&:before': {
-                        content: '""',
-                        display: 'block',
-                        position: 'absolute',
-                        top: 0,
-                        right: 19,
-                        width: 10,
-                        height: 10,
-                        bgcolor: 'background.paper',
-                        transform: 'translateY(-50%) rotate(45deg)',
-                        zIndex: 0,
-                    },
-                },
-            }}
-            transformOrigin={{ horizontal: 'right', vertical: 'top' }}
-            anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
-        >
-            {props.children}
-        </Menu>
-    );
-};
-
 export default ActionBar;

+ 47 - 0
web/src/components/PopupMenu.js

@@ -0,0 +1,47 @@
+import {Menu} from "@mui/material";
+import * as React from "react";
+
+const PopupMenu = (props) => {
+    const horizontal = props.horizontal ?? "left";
+    const arrow = (horizontal === "right") ? { right: 19 } : { left: 19 };
+    return (
+        <Menu
+            anchorEl={props.anchorEl}
+            open={props.open}
+            onClose={props.onClose}
+            onClick={props.onClose}
+            PaperProps={{
+                elevation: 0,
+                sx: {
+                    overflow: 'visible',
+                    filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
+                    mt: 1.5,
+                    '& .MuiAvatar-root': {
+                        width: 32,
+                        height: 32,
+                        ml: -0.5,
+                        mr: 1,
+                    },
+                    '&:before': {
+                        content: '""',
+                        display: 'block',
+                        position: 'absolute',
+                        top: 0,
+                        width: 10,
+                        height: 10,
+                        bgcolor: 'background.paper',
+                        transform: 'translateY(-50%) rotate(45deg)',
+                        zIndex: 0,
+                        ...arrow
+                    },
+                },
+            }}
+            transformOrigin={{ horizontal: horizontal, vertical: 'top' }}
+            anchorOrigin={{ horizontal: horizontal, vertical: 'bottom' }}
+        >
+            {props.children}
+        </Menu>
+    );
+};
+
+export default PopupMenu;

+ 49 - 8
web/src/components/SubscribeDialog.js

@@ -20,6 +20,11 @@ import routes from "./routes";
 import accountApi, {UnauthorizedError} from "../app/AccountApi";
 import IconButton from "@mui/material/IconButton";
 import PublicIcon from '@mui/icons-material/Public';
+import LockIcon from '@mui/icons-material/Lock';
+import PublicOffIcon from '@mui/icons-material/PublicOff';
+import MenuItem from "@mui/material/MenuItem";
+import PopupMenu from "./PopupMenu";
+import ListItemIcon from "@mui/material/ListItemIcon";
 
 const publicBaseUrl = "https://ntfy.sh";
 
@@ -75,11 +80,15 @@ const SubscribePage = (props) => {
     const { t } = useTranslation();
     const [anotherServerVisible, setAnotherServerVisible] = useState(false);
     const [errorText, setErrorText] = useState("");
+    const [accessAnchorEl, setAccessAnchorEl] = useState(null);
+    const [access, setAccess] = useState("public");
     const baseUrl = (anotherServerVisible) ? props.baseUrl : config.baseUrl;
     const topic = props.topic;
     const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.topic));
-    const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)]))
+    const existingBaseUrls = Array
+        .from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)]))
         .filter(s => s !== config.baseUrl);
+
     const handleSubscribe = async () => {
         const user = await userManager.get(baseUrl); // May be undefined
         const username = (user) ? user.username : t("subscribe_dialog_error_user_anonymous");
@@ -97,10 +106,12 @@ const SubscribePage = (props) => {
         console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
         props.onSuccess();
     };
+
     const handleUseAnotherChanged = (e) => {
         props.setBaseUrl("");
         setAnotherServerVisible(e.target.checked);
     };
+
     const subscribeButtonEnabled = (() => {
         if (anotherServerVisible) {
             const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic));
@@ -110,6 +121,7 @@ const SubscribePage = (props) => {
             return validTopic(topic) && !isExistingTopicUrl;
         }
     })();
+
     const updateBaseUrl = (ev, newVal) => {
         if (validUrl(newVal)) {
           props.setBaseUrl(newVal.replace(/\/$/, '')); // strip trailing slash after https?://
@@ -117,6 +129,7 @@ const SubscribePage = (props) => {
           props.setBaseUrl(newVal);
         }
     };
+
     return (
         <>
             <DialogTitle>{t("subscribe_dialog_subscribe_title")}</DialogTitle>
@@ -125,9 +138,13 @@ const SubscribePage = (props) => {
                     {t("subscribe_dialog_subscribe_description")}
                 </DialogContentText>
                 <div style={{display: 'flex'}} role="row">
-                    <IconButton color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
-                        <PublicIcon/>
-                    </IconButton>
+                    {session.exists() &&
+                        <IconButton onClick={(ev) => setAccessAnchorEl(ev.currentTarget)} color="inherit" size="large" edge="start" sx={{height: "45px", marginTop: "5px", color: "grey"}}>
+                            {access === "public" && <PublicIcon/>}
+                            {access === "public-read" && <PublicOffIcon/>}
+                            {access === "private" && <LockIcon/>}
+                        </IconButton>
+                    }
                     <TextField
                         autoFocus
                         margin="dense"
@@ -142,10 +159,34 @@ const SubscribePage = (props) => {
                             maxLength: 64,
                             "aria-label": t("subscribe_dialog_subscribe_topic_placeholder")
                         }}
-                        />
-                        <Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
-                            {t("subscribe_dialog_subscribe_button_generate_topic_name")}
-                        </Button>
+                    />
+                    <Button onClick={() => {props.setTopic(randomAlphanumericString(16))}} style={{flexShrink: "0", marginTop: "0.5em"}}>
+                        {t("subscribe_dialog_subscribe_button_generate_topic_name")}
+                    </Button>
+                    <PopupMenu
+                        anchorEl={accessAnchorEl}
+                        open={!!accessAnchorEl}
+                        onClose={() => setAccessAnchorEl(null)}
+                    >
+                        <MenuItem onClick={() => setAccess("private")} selected={access === "private"}>
+                            <ListItemIcon>
+                                <LockIcon fontSize="small" />
+                            </ListItemIcon>
+                            Only I can publish and subscribe
+                        </MenuItem>
+                        <MenuItem onClick={() => setAccess("public-read")} selected={access === "public-read"}>
+                            <ListItemIcon>
+                                <PublicOffIcon fontSize="small" />
+                            </ListItemIcon>
+                            I can publish, everyone can subscribe
+                        </MenuItem>
+                        <MenuItem onClick={() => setAccess("public")} selected={access === "public"}>
+                            <ListItemIcon>
+                                <PublicIcon fontSize="small" />
+                            </ListItemIcon>
+                            Everyone can publish and subscribe
+                        </MenuItem>
+                    </PopupMenu>
                 </div>
                 <FormControlLabel
                     sx={{pt: 1}}