简体   繁体   English

Firestore:删除文档和安全规则

[英]Firestore: Delete document and security rules

Description 描述

I have a problem dealing with deletion using firestore. 我在使用Firestore处理删除时遇到问题。 In short, I created a security rule for posts like this: 简而言之,我为这样的帖子创建了安全规则:

First there are some functions in the rules: 首先,规则中有一些功能:

service cloud.firestore { 服务cloud.firestore {

function userRoles() {
    return ['admin', 'customer', 'reader'];
}

function userGenders() {
    return ['mal', 'female', 'other'];
}

function postVisibilities() {
    return ['public', 'private', 'protected'];
}

function postType() {
    return ['music', 'motion_design', 'graphic_art'];
}

function isPayment(paymentDoc) {
    return paymentDoc != null
        && paymentDoc.date is timestamp
        && paymentDoc.price is number
        && paymentDoc.price is number
        && paymentDoc.price > 0;
}

function isBill(billDoc) {
    return billDoc.sellerId is string
        && billDoc.buyerId is string
        && billDoc.postIds != null
        && billDoc.date is timestamp
        && billDoc.paymentDoc != null
        && isPayment(billDoc.paymentDoc);
}

function isAccount(accountDoc) {
    return accountDoc.isRegistered is bool
        && accountDoc.addressId is string
        && accountDoc.contactId is string
        && accountDoc.email is string
        && accountDoc.username is string
        && accountDoc.gender is string
        && accountDoc.gender in userGenders()
        && accountDoc.role is string
        && accountDoc.role in userRoles();
}

function isPost(postDoc) {
    return postDoc.createdAt is timestamp
        && postDoc.updatedAt is timestamp
        && postDoc.title is string
        && postDoc.text is string
        && postDoc.image is string
        && postDoc.authorId is string
        && postDoc.visibility is string
        && postDoc.visibility in postVisibilities();
}

function isVote(voteDoc) {
    return voteDoc.authorId is string
        && voteDoc.reaction is string
        && voteDoc.reaction in ['up', 'down'];
}

function isComment(commentDoc) {
    return commentDoc.authorId is string
        && commentDoc.message is string;
}

function isSingle(doc) {
    return doc.size() == 1;
}

match /databases/{database}/documents {

    function userExists(userId) {
        return userId != null && exists(/databases/$(database)/documents/accounts/$(userId));
    }

    function getUserRole(userId) {
        return get(/databases/$(database)/documents/accounts/$(userId)).data.roles;
    }

    function hatUserRole(userId, role) {
        return getRoleForUser(userId) in role;
    }

    match /{document=**} {
        allow read: if true;
        allow write: if false;
    }

    match /accounts/{accountId} {

        allow create: if isAccount(request.resource.data)
                            && (request.auth.uid == accountId || hatUserRole(request.auth.uid, ['admin']));
        allow update: if request.auth.uid == accountId || hatUserRole(request.auth.uid, ['admin']);
        allow delete: if hatUserRole(request.auth.uid, ['admin']);

        match /contacts/{contactId} {
            allow write: if isSingle(request.resource.data)
                        && request.auth.uid == accountId;
            allow read: if userExists(request.auth.uid);
        }

        match /favorites/{favoriteId} {
            allow write: if isSingle(request.resource.data)
                        && request.auth.uid == accountId;
            allow read: if userExists(request.auth.uid);
        }

        match /votes/{voteId} {

            allow create: if isVote(request.resource.data)
                        && userExists(request.auth.uid);
            allow update: if userExists(request.auth.uid)
                        && isVote(request.resource.data)
                        && request.resource.data.authorId == request.auth.uid
            allow delete: if userExists(request.auth.uid)
                        && (request.auth.uid == accountId
                        || hatUserRole(request.auth.uid, ['admin']))
        }
    }

    match /bills/{billId} {
        allow create: if isBill(request.resource.data)
                    && userExists(request.resource.data.sellerId)
                    && userExists(request.resource.data.buyerId)
                    && (request.resource.data.buyerId == request.auth.uid
                    || request.resource.data.sellerId == request.auth.uid);
        allow update, delete: if false;
        allow read: if request.resource.data.buyerId == request.aut.uid
                        || request.resource.data.sellerId == request.aut.uid;
    }

    match /posts/{postId} {

        function publicPost() {
              return get(/databases/$(database)/documents/posts/$(postId)).data.visibility == 'public';
        }

        function postVisibility() {
            return get(/databases/$(database)/documents/posts/$(postId)).data.visibility;
        }

        function protectedPost() {
            return userExists(request.auth.uid)
                && get(/databases/$(database)/documents/posts/$(postId)).data.visibility == 'public';
        }

        function findPostAuthor(pathToFind) {
                return get(/databases/$(database)/documents/posts/$(pathToFind)).data.authorId
        }

        allow create, update: if isPost(request.resource.data)
                    && userExists(request.auth.uid)
                    && request.resource.data.authorId == request.auth.uid;
        allow read: if request.resource.data.visibility == 'public';
        allow delete: if userExists(request.auth.uid)
                    && findPostAuthor(request.resource.id) == request.auth.uid;

        match /votes/{voteId} {
            allow read: if protectedPost(postId)
                        || publicPost(postId);
            allow create: if isVote(request.resource.data)
                        && postVisibility(postId) in ['public', 'protected']
                        && userExists(request.auth.uid);
            allow update: if isVote(request.resource.data)
                        && request.resource.data.authorId == request.auth.uid;
            allow delete: if userExists(request.auth.uid)
                        && (request.auth.uid == request.resource.data.authorId
                        || hatUserRole(request.auth.uid, ['admin']));
        }

        match /comments/{commentId} {
            allow read: if protectedPost(postId) || publicPost(postId);
            allow create: if isComment(request.resource.data)
                        && postVisibility(postId) in ['public', 'protected']
                        && userExists(request.auth.uid);
            allow update: if isComment(request.resource.data)
                        && request.resource.data.authorId == request.auth.uid;
            allow delete: if userExists(request.auth.uid)
                        && (request.auth.uid == request.resource.data.authorId
                        || hatUserRole(request.auth.uid, ['admin']));
        }
    }
}
}

For creation and update, all work fine. 对于创建和更新,一切正常。

After that, I created two methods implementations to delete a document, the two of them using id s which are: 之后,我创建了两个方法实现来删除文档,其中两个使用id

public deletePost(postId: string): Observable<void> {
    const postRef = this.db.collection('posts').doc(postId).ref;

    return fromPromise(this.db.firestore.runTransaction((transaction => {
        return transaction.get(postRef).then(snapshot => {
            if (!snapshot.exists) {
                this.snackBar.open('Post doesn\'t exist', 'close');
            } else {
                const auth = snapshot.data().authorId === this._userId;
                if (auth) {
                    transaction.delete(postRef);
                } else {
                    this.snackBar.open('You\' not allowed to do that!');
                }
            }
        });
    })));
}

using transactions, and: 使用交易,以及:

protected removeElement(elementId: string): Observable<any> {
    return fromPromise(this.db.collection(this.dbCollection).doc(elementId).delete());
}

not using any transaction, simple deletion. 不使用任何交易,简单删除。

Problem 问题

Neither of those method work. 这些方法都不起作用。

When using the first method, I get: 使用第一种方法时,我得到:

ERROR Error: Server responded with status 
at new FirestoreError (index.cjs.js:346)
at T.<anonymous> (index.cjs.js:6901)
at Ab (index.js:23)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.dispatchEvent (index.js:21)
at te (index.js:66)
at ve (index.js:69)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.jb (index.js:67)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.Na (index.js:67)
at XMLHttpRequest.wrapFn (zone.js:1188)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3815)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:496)
at invokeTask (zone.js:1540)
at XMLHttpRequest.globalZoneAwareCallback (zone.js:1566)

and I get with the second one: 我得到第二个:

ERROR Error: Missing or insufficient permissions.
at new FirestoreError (index.cjs.js:346)
at index.cjs.js:7088
at W.<anonymous> (index.cjs.js:7033)
at Ab (index.js:23)
at W.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.dispatchEvent (index.js:21)
at Re.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.Re.Ca (index.js:98)
at ye.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.Oa (index.js:86)
at dd (index.js:42)
at ed (index.js:39)
at ad (index.js:37)
at L.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.Sa (index.js:36)
at L.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.nb (index.js:35)
at Ab (index.js:23)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.dispatchEvent (index.js:21)
at ve (index.js:68)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.jb (index.js:67)
at T.push../node_modules/@firebase/webchannel-wrapper/dist/index.js.g.Na (index.js:67)
at XMLHttpRequest.wrapFn (zone.js:1188)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3815)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:496)
at invokeTask (zone.js:1540)
at XMLHttpRequest.globalZoneAwareCallback (zone.js:1566)

Since I'm not sure if the problem where the problem is, I have some theories: 由于我不确定问题出在哪里,因此我有一些理论:

  • Maybe in the rules , when I write 也许在rules ,当我写

    allow delete: if userExists(request.auth.uid) && findPostAuthor(request.resource.data.id) == request.auth.uid;

    I think, that as I only looking directly at the document using it's Id , the request.resource.data.id mustn't contain anything. 我认为,因为我只使用Id直接查看文档,所以request.resource.data.id不能包含任何内容。

  • I also thought, that if transactions doesn't work, it may be because the way it actually works is really different from what we see on other transactions functions. 我还认为,如果transactions无效,那可能是因为transactions实际方式与我们在其他交易功能上看到的方式确实不同。

As I'm using angularFire2 , this.db => AngularFirestore, this.dbCollection => 'posts' and in the structure of any post, there is a autorId field which is a string. 当我使用angularFire2 ,this.db => AngularFirestore,this.dbCollection =>'posts',在任何帖子的结构中,都有一个autorId字段,它是一个字符串。

For delete , you need to compare request.auth.uid to resource.data.uid , not to request.resource.data.uid . 对于delete ,您需要将request.auth.uidresource.data.uid进行比较,而不是request.resource.data.uid For example: 例如:

match /bookmarks/{id} {
  allow create: if request.resource.data.uid == request.auth.uid
  allow read, delete: if resource.data.uid == request.auth.uid
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM