Update policies for collaborators for stricter rules

This commit is contained in:
thiloho
2024-08-08 16:30:01 +02:00
parent bcc26322d3
commit 837729c83c
8 changed files with 85 additions and 26 deletions

View File

@@ -22,9 +22,9 @@ CREATE TABLE internal.user (
CREATE TABLE internal.website (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL,
title VARCHAR(50) NOT NULL CHECK (trim(title) <> ''),
title VARCHAR(50) NOT NULL CHECK (trim(title) != ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
@@ -56,14 +56,14 @@ CREATE TABLE internal.header (
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL,
CONSTRAINT logo_content_check CHECK (
(logo_type = 'text' AND logo_text IS NOT NULL AND trim(logo_text) <> '') OR
(logo_type = 'text' AND logo_text IS NOT NULL AND trim(logo_text) != '') OR
(logo_type = 'image' AND logo_image IS NOT NULL)
)
);
CREATE TABLE internal.home (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
main_content TEXT NOT NULL CHECK (trim(main_content) <> ''),
main_content TEXT NOT NULL CHECK (trim(main_content) != ''),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
);
@@ -72,12 +72,12 @@ CREATE TABLE internal.article (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES internal.user(id) ON DELETE SET NULL,
title VARCHAR(100) NOT NULL CHECK (trim(title) <> ''),
meta_description VARCHAR(250) CHECK (trim(meta_description) <> ''),
meta_author VARCHAR(100) CHECK (trim(meta_author) <> ''),
title VARCHAR(100) NOT NULL CHECK (trim(title) != ''),
meta_description VARCHAR(250) CHECK (trim(meta_description) != ''),
meta_author VARCHAR(100) CHECK (trim(meta_author) != ''),
cover_image UUID REFERENCES internal.media(id) ON DELETE SET NULL,
publication_date DATE NOT NULL DEFAULT CURRENT_DATE,
main_content TEXT CHECK (trim(main_content) <> ''),
main_content TEXT CHECK (trim(main_content) != ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
@@ -85,7 +85,7 @@ CREATE TABLE internal.article (
CREATE TABLE internal.footer (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) <> ''),
additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) != ''),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
);

View File

@@ -23,7 +23,7 @@ EXECUTE FUNCTION internal.check_role_exists();
CREATE FUNCTION
internal.encrypt_pass() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' OR NEW.password_hash <> OLD.password_hash THEN
IF TG_OP = 'INSERT' OR NEW.password_hash != OLD.password_hash THEN
NEW.password_hash = crypt(NEW.password_hash, gen_salt('bf'));
END IF;
RETURN NEW;

View File

@@ -17,7 +17,7 @@ WITH (security_invoker = on)
AS
SELECT
id,
owner_id,
user_id,
content_type,
title,
created_at,

View File

@@ -20,7 +20,7 @@ BEGIN
SELECT EXISTS (
SELECT 1
FROM internal.website
WHERE id = website_id AND owner_id = _user_id
WHERE id = website_id AND user_id = _user_id
) INTO _has_access;
IF _has_access THEN
@@ -103,7 +103,15 @@ USING (internal.user_has_website_access(website_id, 20));
CREATE POLICY delete_article ON internal.article
FOR DELETE
USING (internal.user_has_website_access(website_id, 30));
USING (
internal.user_has_website_access(website_id, 30)
OR
(
internal.user_has_website_access(website_id, 20)
AND
user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY insert_article ON internal.article
FOR INSERT
@@ -125,15 +133,48 @@ USING (internal.user_has_website_access(website_id, 10));
CREATE POLICY insert_collaborations ON internal.collab
FOR INSERT
WITH CHECK (internal.user_has_website_access(website_id, 30));
WITH CHECK (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
true
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
false
END
);
CREATE POLICY update_collaborations ON internal.collab
FOR UPDATE
USING (internal.user_has_website_access(website_id, 30));
USING (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
true
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
false
END
);
CREATE POLICY delete_collaborations ON internal.collab
FOR DELETE
USING (internal.user_has_website_access(website_id, 30));
USING (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
TRUE
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
FALSE
END
);
-- migrate:down

View File

@@ -4,7 +4,7 @@ WITH (security_invoker = on)
AS
SELECT
w.id,
w.owner_id,
w.user_id,
w.content_type,
w.title,
s.accent_color_light_theme,

View File

@@ -0,0 +1,24 @@
-- migrate:up
CREATE FUNCTION internal.check_user_not_website_owner()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM internal.website
WHERE id = NEW.website_id AND user_id = NEW.user_id
) THEN
RAISE foreign_key_violation USING MESSAGE = 'User cannot be added as a collaborator to their own website';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE CONSTRAINT TRIGGER check_user_not_website_owner
AFTER INSERT ON internal.collab
FOR EACH ROW
EXECUTE FUNCTION internal.check_user_not_website_owner();
-- migrate:down
DROP TRIGGER check_user_not_website_owner ON internal.collab;
DROP FUNCTION internal.check_user_not_website_owner();