1from fts_base import FTSBaseTest, FTSIndex
2from TestInput import TestInputSingleton
3from security.rbac_base import RbacBase
4from lib.membase.api.rest_client import RestConnection
5import json
6import httplib2
7
8class RbacFTS(FTSBaseTest):
9
10    def setUp(self):
11        super(RbacFTS, self).setUp()
12        users = TestInputSingleton.input.param("users", None)
13        self.inp_users = []
14        if users:
15            self.inp_users = eval(eval(users))
16        self.master = self._cb_cluster.get_master_node()
17        self.users = self.get_user_list()
18        self.roles = self.get_user_role_list()
19
20    def tearDown(self):
21        self.delete_role()
22        super(RbacFTS, self).tearDown()
23
24    def create_users(self, users=None):
25        """
26        :param user: takes a list of {'id': 'xxx', 'name': 'some_name ,
27                                        'password': 'passw0rd'}
28        :return: Nothing
29        """
30        if not users:
31            users = self.users
32        RbacBase().create_user_source(users,'builtin',self.master)
33        self.log.info("SUCCESS: User(s) %s created"
34                      % ','.join([user['name'] for user in users]))
35
36    def assign_role(self, rest=None, roles=None):
37        if not rest:
38            rest = RestConnection(self.master)
39        #Assign roles to users
40        if not roles:
41            roles = self.roles
42        RbacBase().add_user_role(roles, rest,'builtin')
43        for user_role in roles:
44            self.log.info("SUCCESS: Role(s) %s assigned to %s"
45                          %(user_role['roles'], user_role['id']))
46
47    def delete_role(self, rest=None, user_ids=None):
48        if not rest:
49            rest = RestConnection(self.master)
50        if not user_ids:
51            user_ids = [user['id'] for user in self.roles]
52        RbacBase().remove_user_role(user_ids, rest)
53        self.sleep(20, "wait for user to get deleted...")
54        self.log.info("user roles revoked for %s" % ", ".join(user_ids))
55
56    def get_user_list(self):
57        """
58        :return:  a list of {'id': 'userid', 'name': 'some_name ,
59        'password': 'passw0rd'}
60        """
61        user_list = []
62        for user in self.inp_users:
63            user_list.append({att: user[att] for att in ('id',
64                                                         'name',
65                                                         'password')})
66        return user_list
67
68    def get_user_role_list(self):
69        """
70        :return:  a list of {'id': 'userid', 'name': 'some_name ,
71         'roles': 'admin:fts_admin[default]'}
72        """
73        user_role_list = []
74        for user in self.inp_users:
75            user_role_list.append({att: user[att] for att in ('id',
76                                                              'name',
77                                                              'roles')})
78        return user_role_list
79
80    def get_rest_handle_for_credentials(self, user , password):
81        rest = RestConnection(self._cb_cluster.get_random_fts_node())
82        rest.username = user
83        rest.password = password
84        return rest
85
86    def create_index_with_no_credentials(self):
87        server = self._cb_cluster.get_random_fts_node()
88        self.load_sample_buckets(server=server,bucketName="travel-sample")
89
90        api = "http://{0}:8094/api/index/travel?sourceType=couchbase&" \
91              "indexType=fulltext-index&sourceName=travel-sample".\
92            format(server.ip)
93        response, content = httplib2.Http(timeout=120).request(api,
94                                                               "PUT",
95                                                               headers=None)
96        self.log.info("Creating index returned : {0}".format(str(response)+ content))
97        if response['status'] in ['200', '201', '202']:
98            self.log.error(content)
99            self.fail("FAIL: Index creation passed without credentials!")
100        else:
101            self.log.info("Index creation without credentials failed as expected!")
102
103    def delete_index_with_no_credentials(self):
104        server = self._cb_cluster.get_random_fts_node()
105        self.load_sample_buckets(server=server, bucketName="travel-sample")
106        index = FTSIndex(self._cb_cluster,
107                         name="travel",
108                         source_name="travel-sample")
109        rest = self.get_rest_handle_for_credentials("Administrator", "password")
110        index.create(rest)
111
112        api = "http://{0}:8094/api/index/travel".format(server.ip)
113        response, content = httplib2.Http(timeout=120).request(api,
114                                                              "DELETE",
115                                                              headers=None)
116        self.log.info("Deleting index definition returned : {0}".format(str(response)+content))
117        if response['status'] in ['200', '201', '202']:
118            self.log.error(content)
119            self.fail("FAIL: Index deletion passed without credentials!")
120        else:
121            self.log.info("Index deletion without credentials failed as expected!")
122
123    def get_index_with_no_credentials(self):
124        server = self._cb_cluster.get_random_fts_node()
125        self.load_sample_buckets(server=server, bucketName="travel-sample")
126        index = FTSIndex(self._cb_cluster,
127                         name="travel",
128                         source_name="travel-sample")
129        rest = self.get_rest_handle_for_credentials("Administrator", "password")
130        index.create(rest)
131
132        api = "http://{0}:8094/api/index/travel".format(server.ip)
133        response, content = httplib2.Http(timeout=120).request(api,
134                                                               "GET",
135                                                               headers=None)
136        self.log.info("Getting index definition returned : {0}".format(str(response)+content))
137        if response['status'] in ['200', '201', '202']:
138            self.log.error(content)
139            self.fail("FAIL: Get index definition passed without credentials!")
140        else:
141            self.log.info("Get index definition without credentials failed as expected!")
142
143    def query_index_with_no_credentials(self):
144        server = self._cb_cluster.get_random_fts_node()
145        self.load_sample_buckets(server=server, bucketName="travel-sample")
146        index = FTSIndex(self._cb_cluster,
147                         name="travel",
148                         source_name="travel-sample")
149        rest = self.get_rest_handle_for_credentials("Administrator", "password")
150        index.create(rest)
151        self.wait_for_indexing_complete()
152
153        api = "http://{0}:8094/api/index/travel/query?".format(server.ip)
154        query = {"match": "Wick", "field": "city"}
155        import urllib
156        api = api + urllib.urlencode(query)
157        response, content = httplib2.Http(timeout=120).request(api,
158                                                               "POST",
159                                                               headers=None)
160        self.log.info("Querying returned : {0}".format(str(response) + content))
161        if response['status'] in ['200', '201', '202']:
162            self.log.error(content)
163            self.fail("FAIL: Querying possible without credentials!")
164        else:
165            self.log.info("Querying without credentials failed as expected!")
166
167    def create_index_with_credentials(self, username, password, index_name,
168                                      bucket_name="default"):
169        index = FTSIndex(self._cb_cluster, name=index_name,
170                         source_name=bucket_name)
171        rest = self.get_rest_handle_for_credentials(username,password)
172        index.create(rest)
173        return index
174
175    def create_alias_with_credentials(self, username, password, alias_name,
176                                      target_indexes):
177        alias_def = {"targets": {}}
178        for index in target_indexes:
179            alias_def['targets'][index.name] = {}
180            alias_def['targets'][index.name]['indexUUID'] = index.get_uuid()
181        alias = FTSIndex(self._cb_cluster, name=alias_name,
182                         index_type='fulltext-alias',index_params=alias_def)
183        rest = self.get_rest_handle_for_credentials(username, password)
184        alias.create(rest)
185        return alias
186
187    def edit_index_with_credentials(self, index, username, password):
188        rest = self.get_rest_handle_for_credentials(username, password)
189        _, defn = index.get_index_defn(rest)
190        self.log.info("Old definition: %s" %defn['indexDef'])
191        new_plan_param = {"maxPartitionsPerPIndex": 10}
192        index.index_definition['planParams'] = \
193            index.build_custom_plan_params(new_plan_param)
194        index.index_definition['uuid'] = index.get_uuid()
195        index.update(rest)
196        _, defn = index.get_index_defn()
197        self.log.info("New definition: %s" %defn['indexDef'])
198
199    def edit_index_different_source(self, index, new_source,
200                                    username, password):
201        rest = self.get_rest_handle_for_credentials(username, password)
202        _, defn = index.get_index_defn(rest)
203        self.log.info("Old definition: %s" % defn['indexDef'])
204        index.index_definition['sourceName'] = new_source
205        index.index_definition['uuid'] = index.get_uuid()
206        index.update(rest)
207        _, defn = index.get_index_defn()
208        self.log.info("New definition: %s" % defn['indexDef'])
209
210
211    def delete_index_with_credentials(self, index, username, password):
212        rest = self.get_rest_handle_for_credentials(username, password)
213        index.delete(rest)
214
215    def query_index_with_credentials(self, index, username, password):
216        rest = self.get_rest_handle_for_credentials(username, password)
217        self.log.info("Now querying with credentials %s:%s" %(username,
218                                                              password))
219        hits, _, _, _ = rest.run_fts_query(index.name,
220                                           {"query": self.sample_query})
221        self.log.info("Hits: %s" %hits)
222
223    def show_user_permissions(self):
224        rest = RestConnection(self._cb_cluster.get_random_fts_node())
225        for user in self.users:
226            for bucket in self._cb_cluster.get_buckets():
227                perm =  RbacBase().check_user_permission(
228                        user['id'],
229                        user['password'],
230                        'cluster.bucket[%s].fts!create,'
231                        'cluster.bucket[%s].fts!manage,'
232                        'cluster.bucket[%s].fts!read,'
233                        'cluster.bucket[%s].fts!write'
234                        %(bucket.name, bucket.name, bucket.name, bucket.name),
235                        rest)
236                self.log.info("Permissions for user: %s on bucket %s is: %s"
237                              %(user['id'], bucket.name,perm))
238
239    def test_fts_admin_permissions(self):
240        """
241        Tests if all created fts admin users are able to create and manage
242        index and aliases
243        :return: nothing
244        """
245        self.load_data()
246        self.create_users()
247        self.assign_role()
248        self.show_user_permissions()
249
250        for user in self.users:
251            for bucket in self._cb_cluster.get_buckets():
252                try:
253
254                    index = self.create_index_with_credentials(
255                        username= user['id'],
256                        password=user['password'],
257                        index_name="%s_%s_idx" %(user['id'],bucket.name),
258                        bucket_name=bucket.name)
259
260                    self.wait_for_indexing_complete()
261                    alias = self.create_alias_with_credentials(
262                        username= user['id'],
263                        password=user['password'],
264                        target_indexes=[index],
265                        alias_name="%s_%s_alias" %(user['id'],bucket.name))
266                    try:
267                        self.edit_index_with_credentials(
268                            index=index,
269                            username=user['id'],
270                            password=user['password'])
271                        self.sleep(60, "Waiting for index rebuild after "
272                                       "update...")
273                        self.query_index_with_credentials(
274                            index=index,
275                            username=user['id'],
276                            password=user['password'])
277                        self.delete_index_with_credentials(
278                            alias,
279                            user['id'],
280                            user['password'])
281                        self.delete_index_with_credentials(
282                            index=index,
283                            username=user['id'],
284                            password=user['password'])
285                    except Exception as e:
286                        self.fail("The user failed to edit/query/delete fts "
287                                  "index %s : %s" % (user['id'], e))
288                except Exception as e:
289                        self.fail("The user failed to create fts index/alias"
290                                  " %s : %s" % (user['id'],e))
291
292    def test_grant_revoke_permissions(self):
293        """
294        Creates an fts admin, creates index with his credentials, revokes
295        his role, limits his permissions to that of an 'fts_searcher'.
296        Tries to see if he can now delete the index he created.
297        Again updates his privileges to a user with no roles.
298        Tests if he is able to get an index count using his new role.
299        """
300        self.load_data()
301        self.create_users()
302        self.assign_role()
303        self.show_user_permissions()
304        for user in self.users:
305            for bucket in self._cb_cluster.get_buckets():
306                index = self.create_index_with_credentials(
307                    username= user['id'],
308                    password=user['password'],
309                    index_name="%s_%s_idx" %(user['id'],bucket.name),
310                    bucket_name=bucket.name)
311                self.delete_role(user_ids=[user['id']])
312                # Temporary work around for deleting role now also deletes user
313                #  in RbacBase()
314                self.create_users(users=[{'id': user['id'],
315                                          'name': user['name'],
316                                          'password': user['password']}])
317                self.assign_role(roles=[{'id': user['id'],
318                                         'name': user['name'],
319                                         'roles': 'fts_searcher[%s]'
320                                                  %bucket.name}])
321                try:
322                    self.delete_index_with_credentials(
323                        index=index,
324                        username=user['id'],
325                        password=user['password'])
326                    self.fail("FAIL: User %s with revoked fts_admin privileges"
327                              " is able to delete index" %(user['id']))
328                except Exception as e:
329                    self.log.info("Expected exception: %s" % e)
330                self.delete_role(user_ids=[user['id']])
331                # Temporary work around for deleting role now also deletes user
332                #  in RbacBase()
333                self.create_users(users=[{'id': user['id'],
334                                          'name': user['name'],
335                                          'password': user['password']}])
336                self.assign_role(roles=[{'id': user['id'],
337                                         'name': user['name'],
338                                         'roles': 'replication_admin'}])
339                try:
340                    rest = self.get_rest_handle_for_credentials(
341                        user=user['id'], password=user['password'])
342                    self.log.info("Index count: %s"
343                                  %index.get_indexed_doc_count(rest))
344                    self.fail("FAIL: Able to get index count even after"
345                              " fts_searcher role was revoked")
346                except Exception as e:
347                    self.log.info("Expected exception: %s" % e)
348
349    def test_fts_searcher_permissions(self):
350        """
351        Tests if an 'fts_searcher' is able to create index, alias (negative test)
352        and if he can get an index count and is able to run a query on a
353        pre-existing index (positive case)
354        """
355        self.load_data()
356        self.create_users()
357        self.assign_role()
358        self.show_user_permissions()
359
360        for user in self.users:
361            for bucket in self._cb_cluster.get_buckets():
362                # creating an index
363                try:
364
365                    self.create_index_with_credentials(
366                        username= user['id'],
367                        password=user['password'],
368                        index_name="%s_%s_idx" %(user['id'],bucket.name),
369                        bucket_name=bucket.name)
370                except Exception as e:
371                    self.log.info("Expected exception: %s" %e)
372                else:
373                    self.fail("An fts_searcher is able to create index!")
374
375                # creating an alias
376                try:
377                    self.log.info("Creating index as administrator...")
378                    index = self.create_index_with_credentials(
379                        username='Administrator',
380                        password='password',
381                        index_name="%s_%s_idx" % ('Admin', bucket.name),
382                        bucket_name=bucket.name)
383                    self.wait_for_indexing_complete()
384                    self.log.info("Creating alias as fts_searcher...")
385                    self.create_alias_with_credentials(
386                        username=user['id'],
387                        password=user['password'],
388                        target_indexes=[index],
389                        alias_name="%s_%s_alias" % (user['id'], bucket.name))
390                except Exception as e:
391                    self.log.info("Expected exception: %s" %e)
392                else:
393                    self.fail("An fts_searcher is able to create alias!")
394
395                # editing an index
396                try:
397                    self.edit_index_with_credentials(index=index,
398                                                     username=user['id'],
399                                                     password=user['password'])
400                except Exception as e:
401                    self.log.info("Expected exception while updating index: %s"
402                                  % e)
403                    self.log.info("Index count: %s"
404                                  %index.get_indexed_doc_count())
405                    self.query_index_with_credentials(index=index,
406                                                     username=user['id'],
407                                                     password=user['password'])
408                else:
409                    self.fail("An fts searcher is able to edit index!")
410
411                # deleting an index
412                try:
413                    self.delete_index_with_credentials(index=index,
414                                                     username=user['id'],
415                                                     password=user['password'])
416                except Exception as e:
417                    self.log.info("Expected exception: %s" % e)
418                else:
419                    self.fail("An fts searcher is able to delete index!")
420
421    def test_fts_alias_creation_multiple_buckets(self):
422        """
423        A slightly complicated input based testcase that will require
424        unnecessarily long manipulation if user info is not hardcoded
425        This test creates 2 users - One(Will) is fts_admin of just one bucket,
426        other(John) of two. We try to create an alias of the indexes using
427        Will's credentials. Ideally this should fail. We then try to create an
428        alias using John's credentials. This should pass.
429        :return: Nothing
430        """
431        self.inp_users = [{"id": "willSmith",
432                            "name": "William Smith",
433                            "password": "password2",
434                            "roles": "fts_admin[default]"},
435                          {"id": "johnDoe",
436                            "name": "Jonathan Downing",
437                            "password": "password1",
438                            "roles": "fts_admin[sasl_bucket_1]:"
439                                     "fts_admin[default]"}]
440        self.users = self.get_user_list()
441        self.roles = self.get_user_role_list()
442        self.load_data()
443        self.create_users()
444        self.assign_role()
445        self.show_user_permissions()
446        indexes = []
447
448        # create indexes for those buckets for which the user is fts admin
449        def_index = self.create_index_with_credentials(
450            username='willSmith',
451            password='password2',
452            index_name='def_index',
453            bucket_name='default')
454        indexes.append(def_index)
455
456        sasl_index = self.create_index_with_credentials(
457            username='johnDoe',
458            password='password1',
459            index_name='sasl_index',
460            bucket_name='sasl_bucket_1')
461        indexes.append(sasl_index)
462
463        # Try creating composite alias using credentials of a user who is
464        #  fts_admin only for 1 bucket
465        try:
466            self.create_alias_with_credentials(username='willSmith',
467                                               password='password2',
468                                               target_indexes=indexes,
469                                               alias_name="wills_alias")
470        except Exception as e:
471            self.log.info("Expected exception: %s" %e)
472
473            # Now try the same with user having fts_admin permissions on
474            # both buckets
475            try:
476                self.create_alias_with_credentials(username='johnDoe',
477                                               password='password1',
478                                               target_indexes=indexes,
479                                               alias_name="johns_alias")
480                self.log.info("SUCCESS: Alias successfully created")
481            except Exception as e:
482                self.fail("The user failed to create a composite alias on "
483                          "indexes that he has permissions to: %s"%e)
484
485    def test_alias_pointing_new_source(self):
486        """
487        This test creates 2 users - One(Will) is fts_admin of just one bucket,
488        other(John) of two. We try to create an index on default bucket using
489        Will's credentials. Then we try to point the index to a different
490        bucket that Will does not have access to. This attempt should fail.
491        Then we have Jonathan change the index source to sasl_bucket_1. And
492        using Will's credentials, we try to get an index count, this should
493        fail as permissions as based on source bucket.
494        :return: Nothing
495        """
496        self.inp_users = [{"id": "willSmith",
497                            "name": "William Smith",
498                            "password": "password2",
499                            "roles": "fts_admin[default]"},
500                          {"id": "johnDoe",
501                            "name": "Jonathan Downing",
502                            "password": "password1",
503                            "roles": "fts_admin[sasl_bucket_1]:"
504                                     "fts_admin[default]"}]
505        self.users = self.get_user_list()
506        self.roles = self.get_user_role_list()
507        self.load_data()
508        self.create_users()
509        self.assign_role()
510        self.show_user_permissions()
511
512        # create indexes for those buckets for which the user is fts admin
513        def_index = self.create_index_with_credentials(
514            username='willSmith',
515            password='password2',
516            index_name='def_index',
517            bucket_name='default')
518        try:
519            self.edit_index_different_source(index = def_index,
520                                         new_source="sasl_bucket_1",
521                                         username='willSmith',
522                                         password='password2')
523            self.fail("User is able to point his index to bucket he does "
524                      "not have access to!")
525        except Exception as e:
526            self.log.info("Expected exception: %s" %e)
527
528        try:
529            self.edit_index_different_source(index=def_index,
530                                             new_source="sasl_bucket_1",
531                                             username='johnDoe',
532                                             password='password1')
533            try:
534                # Have Will get an index count on the index he created
535                # but is now pointed to a different bucket that he has no
536                # access to
537                rest = self.get_rest_handle_for_credentials('willSmith',
538                                                            'password1')
539                index_count = def_index.get_indexed_doc_count(rest)
540                self.fail("User with no permissions on source bucket is able to"
541                          " get an index count on the index")
542            except Exception as e:
543                self.log.info("Expected exception: %s" %e)
544        except Exception as e:
545            self.fail("User with permissions is unable to change the "
546                      "index source bucket %s" %e)
547
548