说明

我在使用firestore处理删除时遇到问题 . 简而言之,我为这样的帖子创建了一个安全规则:

首先,规则中有一些功能:

service 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']));
        }
    }
}
}

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

之后,我创建了两个删除文档的方法实现,其中两个使用 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!');
                }
            }
        });
    })));
}

使用交易,和:

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) && findPostAuthor(request.resource.data.id) == request.auth.uid;

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

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

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