summaryrefslogtreecommitdiff
path: root/jstests/auth/log_user_basic.js
blob: 9657176434c39640250135f9f18e56afaa9d4e73 (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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/**
 * This file tests that "user:<username>@<db>" shows up in the logs.
 */

/**
 * Extracts information from a mongod/mongos log entry.
 *
 * @param line {string} a single line of log.
 *
 * @return {Object} format:
 *
 *     {
 *       id: <string>, // thread id of the log line.
 *       // list of users logged in. Can be empty.
 *       users: <Object> // map of db name to user name
 *     }
 */
var parseLog = function(line) {
    var THREAD_ID_PATTERN = / [012]?\d:\d\d:\d\d\.\d\d\d \[(.+)\] /;
    var ID_USER_PATTERN = new RegExp(THREAD_ID_PATTERN.source + 'user:([^ ]*) ');
    var res = THREAD_ID_PATTERN.exec(line);

    if (res == null) {
        return null;
    }

    var logInfo = { id: res[1], users: {} };

    var userLog = null;
    res = ID_USER_PATTERN.exec(line);

    if (res != null) {
        userLog = res[2];
        // should not have trailing commas
        assert.neq(',', userLog[userLog.length - 1], 'Bad user log list format: ' + line);

        userLog.split(',').forEach(function(userData) {
            var userAndDB = userData.split('@');
            assert.eq(2, userAndDB.length, 'Bad user db pair format: ' + userData +
             ' from line: ' + line);
            logInfo.users[userAndDB[1]] = userAndDB[0];
        });
    }

    return logInfo;
};

/**
 * Performs a series of test on user id logging.
 *
 * @param conn1 {Mongo} the connection object to use for logging in users.
 * @param conn2 {Mongo} another connection object different from conn1.
 */
var doTest = function(conn1, conn2) {
    var connInfo1 = {
        id: null, // thread id of this connection
        mongo: conn1, // connection object
        users: {} // contains authenticated users represented as a map of db to user names.
    };

    var connInfo2 = {
      id: null, mongo: conn2, users: {}
    };

    var conn1Auth = [
        { user: 'foo', pwd: 'bar', db: 'test' },
        { user: 'chun', pwd: 'li', db: 'sf' }
    ];

    var conn2Auth = [
        { user: 'root', pwd: 'ugat', db: 'admin' },
        { user: 'elbow', pwd: 'freeze', db: 'bboy' }
    ];

    var loginUser = function(connInfo, connAuth) {
        var db = connInfo.mongo.getDB(connAuth.db);
        db.addUser(connAuth.user, connAuth.pwd);
        db.auth(connAuth.user, connAuth.pwd);
        connInfo.users[connAuth.db] = connAuth.user;
    };

    var logoutUser = function(connInfo, connAuth) {
        var db = connInfo.mongo.getDB(connAuth.db);
        db.runCommand({ logout: 1 });
        delete connInfo.users[connAuth.db];
    };

    /**
     * Performs a couple of test to make sure that the format of the log is correct.
     * Also checks that whether the right users show up in the logs.
     *
     * @param log {Array.<string>} list of log lines to check.
     * @param connInfo {Object}
     */
    var checkLogs = function(log, connInfo) {
        var foundOne = false;

        /**
         * @return true if the logInfo contains the same users as connIfo.
         */
        var checkUsers = function(logInfo) {
            for (var db in logInfo.users) {
                if (logInfo.users.hasOwnProperty(db) &&
                    logInfo.users[db] != connInfo.users[db]) {
                    return false;
                }
            }

            for (db in connInfo.users) {
                if (connInfo.users.hasOwnProperty(db) &&
                    logInfo.users[db] != connInfo.users[db]) {
                    return false;
                }
            }

            return true;
        };

        var hasUser = function(logInfo) {
            for (var db in logInfo.users) {
                if (logInfo.users.hasOwnProperty(db)) {
                    return true;
                }
            }

            return false;
        };

        log.forEach(function(line) {
            var logInfo = parseLog(line);

            if (logInfo == null) return;
            if (connInfo.id == null) {
                if (checkUsers(logInfo)) {
                    connInfo.id = logInfo.id;
                    foundOne = true;
                }

                return;
            }

            if (logInfo.id == connInfo.id) {
                foundOne = true;
                assert(checkUsers(logInfo), 'logged users does not match [' +
                    tojson(connInfo.users) + '], log: ' + line);
            }
            else if(hasUser(logInfo)) {

                assert(!checkUsers(logInfo), 'Unexpected user log on another thread: ' + line);
            }
        });

        assert(foundOne, 'User log not found in: ' + tojson(log));
    };

    var testDB1 = connInfo1.mongo.getDB('test');
    var testDB2 = connInfo2.mongo.getDB('test');

    // Note: The succeeding tests should not be re-ordered.
    (function() {
        jsTest.log('Test single user on 1 connection.');
        loginUser(connInfo1, conn1Auth[0]);
        testDB1.runCommand({ dbStats: 1 });
        var log = testDB1.adminCommand({ getLog: 'global' });
        checkLogs(log.log, connInfo1);
    })();

    (function() {
        jsTest.log('Test multiple conn with 1 user each');
        loginUser(connInfo2, conn2Auth[0]);
        testDB2.runCommand({ dbStats: 1 });
        var log = testDB1.adminCommand({ getLog: 'global' });
        checkLogs(log.log, connInfo2);
    })();

    (function(){
        jsTest.log('Test multiple conn with 1 multiple user');
        loginUser(connInfo1, conn1Auth[1]);
        var log = testDB1.adminCommand({ getLog: 'global' });
        var lastLogLine = log.log.pop(); // Used for trimming out logs before this point.
        testDB1.runCommand({ dbStats: 1 });
        log = testDB1.adminCommand({ getLog: 'global' });

        // Remove old log entries.
        while (log.log.shift() != lastLogLine) { }
        assert(log.log.length > 0);
        checkLogs(log.log, connInfo1);
    })();

    (function(){
        jsTest.log('Test multiple conn with multiple users each');
        loginUser(connInfo2, conn2Auth[1]);
        var log = testDB2.adminCommand({ getLog: 'global' });
        var lastLogLine = log.log.pop(); // Used for trimming out logs before this point.
        testDB1.runCommand({ dbStats: 1 });
        log = testDB2.adminCommand({ getLog: 'global' });

        // Remove old log entries.
        while (log.log.shift() != lastLogLine) { }
        assert(log.log.length > 0);
        checkLogs(log.log, connInfo2);
    })();

    (function(){
        // Case for logout older user first.
        jsTest.log('Test log line will not show foo');
        logoutUser(connInfo1, conn1Auth[0]);
        var log = testDB1.adminCommand({ getLog: 'global' });
        var lastLogLine = log.log.pop(); // Used for trimming out logs before this point.
        testDB1.runCommand({ dbStats: 1 });
        log = testDB1.adminCommand({ getLog: 'global' });

        // Remove old log entries.
        while (log.log.shift() != lastLogLine) { }
        assert(log.log.length > 0);
        checkLogs(log.log, connInfo1);
    })();

    (function(){
        jsTest.log('Test that log for conn1 will not show \'user:\'');
        logoutUser(connInfo1, conn1Auth[1]);
        var log = testDB1.adminCommand({ getLog: 'global' });
        var lastLogLine = log.log.pop(); // Used for trimming out logs before this point.
        testDB1.runCommand({ dbStats: 1 });
        log = testDB1.adminCommand({ getLog: 'global' });

        // Remove old log entries.
        while (log.log.shift() != lastLogLine) { }
        assert(log.log.length > 0);
        checkLogs(log.log, connInfo1);
    })();

    (function(){
        // Case for logout newer user first.
        jsTest.log('Test log line will not show elbow');
        logoutUser(connInfo2, conn2Auth[1]);
        var log = testDB2.adminCommand({ getLog: 'global' });
        var lastLogLine = log.log.pop(); // Used for trimming out logs before this point.
        testDB1.runCommand({ dbStats: 1 });
        log = testDB2.adminCommand({ getLog: 'global' });

        // Remove old log entries.
        while (log.log.shift() != lastLogLine) { }
        assert(log.log.length > 0);
        checkLogs(log.log, connInfo2);
    })();
};

var mongo = MongoRunner.runMongod({ verbose: 5, setParameter: 'logUserIds=1' });
doTest(mongo, new Mongo(mongo.host));
MongoRunner.stopMongod(mongo.port);

var st = new ShardingTest({ shards: 1, verbose: 5,
    other: { mongosOptions: { setParameter: 'logUserIds=1' }}});
doTest(st.s, new Mongo(st.s.host));
st.stop();