summaryrefslogtreecommitdiff
path: root/jstests/auth/views_authz.js
blob: 6223312249c34229f638fcfd6610d064224413fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
 * Tests authorization special cases with views. These are special exceptions that prohibit certain
 * operations on views even if the user has an explicit privilege on that view.
 * @tags: [requires_sharding]
 */
(function() {
"use strict";

// TODO SERVER-35447: Multiple users cannot be authenticated on one connection within a session.
TestData.disableImplicitSessions = true;

function runTest(conn) {
    // Create the admin user.
    let adminDB = conn.getDB("admin");
    assert.commandWorked(adminDB.runCommand({createUser: "admin", pwd: "admin", roles: ["root"]}));
    assert.eq(1, adminDB.auth("admin", "admin"));

    const viewsDBName = "views_authz";
    let viewsDB = adminDB.getSiblingDB(viewsDBName);
    viewsDB.dropAllUsers();
    viewsDB.logout();

    // Create a user who can read, create and modify a view 'view' and a read a namespace
    // 'permitted' but does not have access to 'forbidden'.
    assert.commandWorked(viewsDB.runCommand({
        createRole: "readWriteView",
        privileges: [
            {
                resource: {db: viewsDBName, collection: "view"},
                actions: ["find", "createCollection", "collMod"]
            },
            {resource: {db: viewsDBName, collection: "view2"}, actions: ["find"]},
            {resource: {db: viewsDBName, collection: "permitted"}, actions: ["find"]}
        ],
        roles: []
    }));
    assert.commandWorked(
        viewsDB.runCommand({createUser: "viewUser", pwd: "pwd", roles: ["readWriteView"]}));

    adminDB.logout();
    assert.eq(1, viewsDB.auth("viewUser", "pwd"));

    const lookupStage = {$lookup: {from: "forbidden", localField: "x", foreignField: "x", as: "y"}};
    const graphLookupStage = {
        $graphLookup:
            {from: "forbidden", startWith: [], connectFromField: "x", connectToField: "x", as: "y"}
    };

    // You cannot create a view if you have both the 'createCollection' and 'find' actions on
    // that view but not the 'find' action on all of the dependent namespaces.
    assert.commandFailedWithCode(viewsDB.createView("view", "forbidden", []),
                                 ErrorCodes.Unauthorized,
                                 "created a readable view on an unreadable collection");
    assert.commandFailedWithCode(viewsDB.createView("view", "permitted", [lookupStage]),
                                 ErrorCodes.Unauthorized,
                                 "created a readable view on an unreadable collection via $lookup");
    assert.commandFailedWithCode(
        viewsDB.createView("view", "permitted", [graphLookupStage]),
        ErrorCodes.Unauthorized,
        "created a readable view on an unreadable collection via $graphLookup");
    assert.commandFailedWithCode(
        viewsDB.createView("view", "permitted", [{$facet: {a: [lookupStage]}}]),
        ErrorCodes.Unauthorized,
        "created a readable view on an unreadable collection via $lookup in a $facet");
    assert.commandFailedWithCode(
        viewsDB.createView("view", "permitted", [{$facet: {b: [graphLookupStage]}}]),
        ErrorCodes.Unauthorized,
        "created a readable view on an unreadable collection via $graphLookup in a $facet");

    assert.commandWorked(viewsDB.createView("view", "permitted", [{$match: {x: 1}}]));

    // You cannot modify a view if you have both the 'collMod' and 'find' actions on that view
    // but not the 'find' action on all of the dependent namespaces.
    assert.commandFailedWithCode(
        viewsDB.runCommand({collMod: "view", viewOn: "forbidden", pipeline: [{$match: {}}]}),
        ErrorCodes.Unauthorized,
        "modified a view to read an unreadable collection");
    assert.commandFailedWithCode(
        viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [lookupStage]}),
        ErrorCodes.Unauthorized,
        "modified a view to read an unreadable collection via $lookup");
    assert.commandFailedWithCode(
        viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [graphLookupStage]}),
        ErrorCodes.Unauthorized,
        "modified a view to read an unreadable collection via $graphLookup");
    assert.commandFailedWithCode(
        viewsDB.runCommand(
            {collMod: "view", viewOn: "permitted", pipeline: [{$facet: {a: [lookupStage]}}]}),
        ErrorCodes.Unauthorized,
        "modified a view to read an unreadable collection via $lookup in a $facet");
    assert.commandFailedWithCode(
        viewsDB.runCommand(
            {collMod: "view", viewOn: "permitted", pipeline: [{$facet: {b: [graphLookupStage]}}]}),
        ErrorCodes.Unauthorized,
        "modified a view to read an unreadable collection via $graphLookup in a $facet");

    // When auth is enabled, users must specify both "viewOn" and "pipeline" when running
    // collMod on a view; specifying only one or the other is not allowed. Without both the
    // "viewOn" and "pipeline" specified, authorization checks cannot determine if the users
    // have the necessary privileges.
    assert.commandFailedWithCode(viewsDB.runCommand({collMod: "view", pipeline: []}),
                                 ErrorCodes.InvalidOptions,
                                 "modified a view without having to specify 'viewOn'");
    assert.commandFailedWithCode(viewsDB.runCommand({collMod: "view", viewOn: "other"}),
                                 ErrorCodes.InvalidOptions,
                                 "modified a view without having to specify 'pipeline'");

    // Create a view on a forbidden collection and populate it.
    assert.eq(1, adminDB.auth("admin", "admin"));
    assert.commandWorked(viewsDB.createView("view2", "forbidden", []));
    for (let i = 0; i < 10; i++) {
        assert.writeOK(viewsDB.forbidden.insert({x: 1}));
    }
    adminDB.logout();

    // Performing a find on a readable view returns a cursor that allows us to perform a getMore
    // even if the underlying collection is unreadable.
    assert.commandFailedWithCode(viewsDB.runCommand({find: "forbidden"}),
                                 ErrorCodes.Unauthorized,
                                 "successfully performed a find on an unreadable namespace");
    let res = viewsDB.runCommand({find: "view2", batchSize: 1});
    assert.commandWorked(res, "could not perform a find on a readable view");
    assert.eq(res.cursor.ns,
              "views_authz.view2",
              "performing find on a view does not return a cursor on the view namespace");
    assert.commandWorked(viewsDB.runCommand({getMore: res.cursor.id, collection: "view2"}),
                         "could not perform getMore on a readable view");
}

// Run the test on a standalone.
let mongod = MongoRunner.runMongod({auth: "", bind_ip: "127.0.0.1"});
runTest(mongod);
MongoRunner.stopMongod(mongod);

// Run the test on a sharded cluster.
// TODO: Remove 'shardAsReplicaSet: false' when SERVER-32672 is fixed.
let cluster = new ShardingTest({
    shards: 1,
    mongos: 1,
    keyFile: "jstests/libs/key1",
    other: {shardOptions: {auth: ""}, shardAsReplicaSet: false}
});
runTest(cluster);
cluster.stop();
}());