Firestore:删除文档和安全规则

Posted

技术标签:

【中文标题】Firestore:删除文档和安全规则【英文标题】:Firestore: Delete document and security rules 【发布时间】:2019-02-01 03:03:24 【问题描述】:

说明

我在使用 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']));
        
    


对于创建和更新,一切正常。

之后,我创建了两个方法实现来删除一个文档,其中两个使用ids,它们是:

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!');
                
            
        );
    )));

使用交易,并且:

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

不使用任何事务,简单删除。

问题

这些方法都不起作用。

当使用第一种方法时,我得到:

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)

我得到了第二个:

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)

由于我不确定问题出在哪里,所以我有一些理论:

也许在rules,我写的时候

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

我认为,由于我只使用Id 直接查看文档,因此request.resource.data.id 不能包含任何内容。

我还想,如果transactions 不起作用,可能是因为它的实际工作方式与我们在其他交易功能上看到的确实不同。

当我使用angularFire2, this.db => AngularFirestore, this.dbCollection => 'posts' 并且在任何帖子的结构中,都有一个autorId 字段,它是一个字符串。

【问题讨论】:

请将您的规则和代码添加为文本而不是图像。 此外,您的规则中调用了一些函数,这些函数未显示在您的图像中 嗨@JasonBerryman,我已经更新了整篇文章,并设置了所有规则。 @AndréKool,我现在使用纯文本。 【参考方案1】:

对于delete,您需要将request.auth.uidresource.data.uid 进行比较,而不是与request.resource.data.uid。例如:

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

【讨论】:

仅使用 resource.data.uid 对我来说也是正确的解决方案。我也试图检查请求。这个解决方案不错。

以上是关于Firestore:删除文档和安全规则的主要内容,如果未能解决你的问题,请参考以下文章

为啥 firebase_firestore 安全规则不适用于文档读取

每个文档颤动的 Firebase-firestore 安全规则

Firestore 安全规则,允许用户查询单个文档

Firestore 安全规则 - 允许基于其父文档数据读取子集合

有没有办法使用Firestore安全规则使用FieldValue.increment()更新文档字段?

即使firestore安全规则中的文档字段不可用,也如何允许访问?