Repository: box/box-python-sdk
Pull request #335 (2018-08-22 00:39:15)

Collaboration Whitelist v2

boxcla: Hi @carycheng, thanks for the pull request. Before we can merge it, we need you to sign our Contributor License Agreement. You can do so electronically here: http://opensource.box.com/cla

Once you have signed, just add a comment to this pull request saying, "CLA signed". Thanks!
coveralls: ## Pull Request Test Coverage Report for [Build 1103](https://coveralls.io/builds/19415088)

* **42** of **44**   **(95.45%)**  changed or added relevant lines in **4** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.02%**) to **94.546%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [boxsdk/client/client.py](https://coveralls.io/builds/19415088/source?filename=boxsdk%2Fclient%2Fclient.py#L187) | 5 | 7 | 71.43%
<!-- | **Total:** | **42** | **44** | **95.45%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/19415088/badge)](https://coveralls.io/builds/19415088) |
| :-- | --: |
| Change from base [Build 1095](https://coveralls.io/builds/19377977): |  0.02% |
| Covered Lines: | 2115 |
| Relevant Lines: | 2237 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index 2c29d00..2da94fc 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -9,6 +9,7 @@
 from ..util.api_call_decorator import api_call
 from ..object.search import Search
 from ..object.events import Events
+from ..object.collaboration_whitelist import CollaborationWhitelist
 from ..object.trash import Trash
 from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
 from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
@@ -160,6 +161,47 @@ def collaboration(self, collab_id):
         """
         return self.translator.translate('collaboration')(session=self._session, object_id=collab_id)
 
+    def collaboration_whitelist(self):
+        """
+        Initilializes a :class:`CollaborationWhitelist` object.
+
+        :return:
+            A :class:`CollaborationWhitelist` object.
+        :rype:
+            :class:`CollaborationWhitelist`.
+        """
+        return CollaborationWhitelist(self._session)
+
+    def collaboration_whitelist_entry(self, entry_id):
+        """
+        Initialize a :class:`CollaborationWhitelistEntry` object, whose box id is entry_id.
+
+        :param entry_id:
+            The box id of the :class:`CollaborationWhitelistEntry` object.
+        :type entry_id:
+            `unicode`
+        :return:
+            A :class:`CollaborationWhitelistEntry` object with the given entry id.
+        :rtype:
+            :class:`CollaborationWhitelistEntry`
+        """
+        return self.translator.get('collaboration_whitelist_entry')(session=self._session, object_id=entry_id)
+
+    def collaboration_whitelist_exempt_target(self, target_id):
+        """
+        Initialize a :class:`CollaborationWhitelistExemptTarget` object, whose box id is target_id.
+
+        :param target_id:
+            The box id of the :class:`CollaborationWhitelistExemptTarget` object.
+        :type target_id:
+            `unicode`
+        :return:
+            A :class:`CollaborationWhitelistExemptTarget` object with the given target id.
+        :rtype:
+            :class:`CollaborationWhitelistExemptTarget`
+        """
+        return self.translator.get('collaboration_whitelist_exempt_target')(session=self._session, object_id=target_id)
+
     def trash(self):
         """
         Initialize a :class:`Trash` object.
diff --git a/boxsdk/object/__init__.py b/boxsdk/object/__init__.py
index a4b467f..a3989af 100644
--- a/boxsdk/object/__init__.py
+++ b/boxsdk/object/__init__.py
@@ -7,6 +7,9 @@
 
 __all__ = list(map(str, [
     'collaboration',
+    'collaboration_whitelist',
+    'collaboration_whitelist_entry',
+    'collaboration_whitelist_exempt_target',
     'collection',
     'comment',
     'device_pinner',
diff --git a/boxsdk/object/collaboration_whitelist.py b/boxsdk/object/collaboration_whitelist.py
new file mode 100644
index 0000000..97713b0
--- /dev/null
+++ b/boxsdk/object/collaboration_whitelist.py
@@ -0,0 +1,141 @@
+# coding: utf-8
+from __future__ import unicode_literals, absolute_import
+
+import json
+
+from .base_endpoint import BaseEndpoint
+from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
+from ..util.api_call_decorator import api_call
+from ..util.text_enum import TextEnum
+
+
+class WhitelistDirection(TextEnum):
+    """
+    Used to determine the direction of the whitelist.
+    """
+    INBOUND = 'inbound'
+    OUTBOUNT = 'outbound'
+    BOTH = 'both'
+
+
+class CollaborationWhitelist(BaseEndpoint):
+    """Represents the whitelist of email domains that users in an enterprise may collaborate with."""
+
+    @api_call
+    def get_entries(self, limit=None, marker=None, fields=None):
+        """
+        Get the entries in the collaboration whitelist using limit-offset paging.
+
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from.
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request.
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the whitelist.
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self.get_url('collaboration_whitelist_entries'),
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
+
+    @api_call
+    def add_domain(self, domain, direction):
+        """
+        Add a new domain to the collaboration whitelist.
+
+        :param domain:
+            The email domain to add to the whitelist.
+        :type domain:
+            `unicode`
+        :param direction:
+            The direction in which collaboration should be allowed: 'inbound', 'outbound', or 'both'.
+        :type direction:
+            `unicode`
+        :returns:
+            The created whitelist entry for the domain.
+        :rtype:
+            :class:`CollaborationWhitelistEntry`
+        """
+        url = self.get_url('collaboration_whitelist_entries')
+        data = {
+            'domain': domain,
+            'direction': direction
+        }
+        response = self._session.post(url, data=json.dumps(data)).json()
+        return self.translator.translate(response['type'])(
+            session=self._session,
+            object_id=response['id'],
+            response_object=response,
+        )
+
+    @api_call
+    def get_exemptions(self, limit=None, marker=None, fields=None):
+        """
+        Get the list of exempted users who are not subject to the collaboration whitelist rules.
+
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from.
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request.
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the exemptions to the whitelist.
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self.get_url('collaboration_whitelist_exempt_targets'),
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
+
+    @api_call
+    def add_exemption(self, user):
+        """
+        Exempt a user from the collaboration whitelist.
+
+        :param user:
+            The user to exempt from the whitelist.
+        :type user:
+            :class:`User`
+        :returns:
+            The created whitelist exemption.
+        :rtype:
+            :class:`CollaborationWhitelistExemptTarget`
+        """
+        url = self.get_url('collaboration_whitelist_exempt_targets')
+        data = {
+            'user': {
+                'id': user.object_id  # pylint:disable=protected-access
+            }
+        }
+        response = self._session.post(url, data=json.dumps(data)).json()
+        return self.translator.translate(response['type'])(
+            session=self._session,
+            object_id=response['id'],
+            response_object=response,
+        )
diff --git a/boxsdk/object/collaboration_whitelist_entry.py b/boxsdk/object/collaboration_whitelist_entry.py
new file mode 100644
index 0000000..3914a39
--- /dev/null
+++ b/boxsdk/object/collaboration_whitelist_entry.py
@@ -0,0 +1,19 @@
+# coding: utf-8
+from __future__ import unicode_literals, absolute_import
+from .base_object import BaseObject
+
+
+class CollaborationWhitelistEntry(BaseObject):
+    """Represents a whitelisted email domain for enterprise collaboration."""
+    _item_type = 'collaboration_whitelist_entry'
+
+    def get_url(self, *args):
+        """
+        Gets the collaboration whitelist entries endpoint URL.
+
+        :return:
+            The collaboration whitelist entries endpoint URL.
+        :rtype:
+            `unicode`
+        """
+        return self._session.get_url('collaboration_whitelist_entries', self._object_id, *args)
diff --git a/boxsdk/object/collaboration_whitelist_exempt_target.py b/boxsdk/object/collaboration_whitelist_exempt_target.py
new file mode 100644
index 0000000..52620de
--- /dev/null
+++ b/boxsdk/object/collaboration_whitelist_exempt_target.py
@@ -0,0 +1,9 @@
+# coding: utf-8
+from __future__ import unicode_literals, absolute_import
+from .base_object import BaseObject
+
+
+class CollaborationWhitelistExemptTarget(BaseObject):
+    """Represents a user who is exempted from the collaboration whitelist."""
+
+    _item_type = 'collaboration_whitelist_exempt_target'
diff --git a/docs/collaboration_whitelist.md b/docs/collaboration_whitelist.md
new file mode 100644
index 0000000..e79cc3c
--- /dev/null
+++ b/docs/collaboration_whitelist.md
@@ -0,0 +1,103 @@
+Collaboration Whitelist
+=======================
+
+The Collaboration Whitelist API allows you to manage a set of approved domains (e.g. whitelist) that can collaborate with your enterprise. You can also manage whether the whitelisted domains are approved for outbound or inbound collaboration.
+
+It is important to note that the collaboration whitelist functionality is only available to customers with Box Governance.
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+
+
+- [List Collaboration Whitelist Entries](#list-collaboration-whitelist-entries)
+- [Get Information for Collaboration Whitelist Entry](#get-information-for-collaboration-whitelist-entry)
+- [Whitelist a Domain for Collaboration](#whitelist-a-domain-for-collaboration)
+- [Remove a Domain from Whitelist](#remove-a-domain-from-whitelist)
+- [List Exempt Users](#list-exempt-users)
+- [Get Exempt User Information](#get-exempt-user-information)
+- [Exempt User from Whitelist](#exempt-user-from-whitelist)
+- [Remove User Exemption](#remove-user-exemption)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+List Collaboration Whitelist Entries
+------------------------------------
+
+To retrieve a list of collaboration whitelist entries, use `collaboration_whitelist.get_entries(limit=None, marker=None, fields=None)`
+
+```python
+whitelist_entries = client.collaboration_whitelist().get_entries()
+for whitelist_entry in whitelist_entries:
+    # Do something
+```
+
+Get Information for Collaboration Whitelist Entry
+-------------------------------------------------
+
+To get information about a collaboration whitelist entry, use `collaboration_whitelist_entry.get(fields=None)`.
+
+```python
+collaboration_whitelist_entry_info = client.collaboration_whitelist_entry('11111').get()
+```
+
+Whitelist a Domain for Collaboration
+------------------------------------
+
+To whitelist a domain for collaboration, use `collaboration_whitelist.add_domain(domain, direction)`.
+
+You can determine the direction of the whitelist by passing in 'outbound', 'inbound', or 'both'. Outbound collaboration is defined as
+a user in your enterprise collaborating on content owned by someone outside your enterprise. Inbound collaboration is defined as a user outside of your enterprise collaborating on content owned by your enterprise.
+
+```python
+from boxsdk.object.collaboration_whitelist import WhitelistDirection
+domain = 'example.com'
+whitelist_entry = client.collaboration_whitelist().add_domain(domain=domain, direction=WhitelistDirection.INBOUND)
+```
+
+Remove a Domain from Whitelist
+------------------------------
+
+To remove a collaboration whitelisted domain, use `collaboration_whitelist_entry.delete()`
+
+```python
+client.collaboration_whitelist_entry('11111').delete()
+```
+
+List Exempt Users
+-----------------
+
+To receive an iterable of exempt users from collaboration whitelist, use `collaboration_whitelist.get_exemptions(limit=None, marker=None, fields=None)`.
+
+```python
+exempt_users = client.collaboration_whitelist().get_exemptions()
+for exempt_user in exempt_users:
+    # Do something...
+```
+
+Get Exempt User Information
+---------------------------
+
+To get information about an exempted user, use `collaboration_whitelist_exempt_target.get(fields=None)`.
+
+```python
+exempt_user_info = client.collaboration_whitelist_exempt_target('11111').get()
+```
+
+Exempt User from Whitelist
+--------------------------
+
+To exempt a user from a collaboration whitelist, use `collaboration_whitelist.add_exemption(user)`.
+
+```python
+user = client.user('11111')
+exempted_user = client.collaboration_whitelist().add_exemption(user)
+```
+
+Remove User Exemption
+---------------------
+
+To remove a user from a collaboration whitelist, use `collaboration_whitelist_exempt_target.delete()`.
+
+```python
+client.collaboration_whitelist_exempt_target('22222').delete()
+```


Test patch:
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index 3cf4cef..85d0797 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -18,6 +18,7 @@
 from boxsdk.config import API
 from boxsdk.network.default_network import DefaultNetworkResponse
 from boxsdk.object.collaboration import Collaboration
+from boxsdk.object.collaboration_whitelist import CollaborationWhitelist
 from boxsdk.object.events import Events
 from boxsdk.object.folder import Folder
 from boxsdk.object.file import File
@@ -415,6 +416,11 @@ def test_events_returns_event_object(mock_client):
     assert isinstance(mock_client.events(), Events)
 
 
+def test_collaboration_whitelist_initializer(mock_client):
+    collaboration_whitelist = mock_client.collaboration_whitelist()
+    assert isinstance(collaboration_whitelist, CollaborationWhitelist)
+
+
 def test_get_groups_return_the_correct_group_objects(
         mock_client,
         mock_box_session,
diff --git a/test/unit/object/conftest.py b/test/unit/object/conftest.py
index 201685d..ef1ea1f 100644
--- a/test/unit/object/conftest.py
+++ b/test/unit/object/conftest.py
@@ -22,6 +22,9 @@
 from boxsdk.object.retention_policy import RetentionPolicy
 from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment
 from boxsdk.object.search import Search
+from boxsdk.object.collaboration_whitelist import CollaborationWhitelist
+from boxsdk.object.collaboration_whitelist_entry import CollaborationWhitelistEntry
+from boxsdk.object.collaboration_whitelist_exempt_target import CollaborationWhitelistExemptTarget
 from boxsdk.object.webhook import Webhook
 from boxsdk.object.task import Task
 from boxsdk.object.task_assignment import TaskAssignment
@@ -93,6 +96,21 @@ def test_comment(mock_box_session, mock_object_id):
     return Comment(mock_box_session, mock_object_id)
 
 
+@pytest.fixture()
+def test_collaboration_whitelist(mock_box_session):
+    return CollaborationWhitelist(mock_box_session)
+
+
+@pytest.fixture()
+def test_collaboration_whitelist_entry(mock_box_session, mock_object_id):
+    return CollaborationWhitelistEntry(mock_box_session, mock_object_id)
+
+
+@pytest.fixture()
+def test_collaboration_whitelist_exemption(mock_box_session, mock_object_id):
+    return CollaborationWhitelistExemptTarget(mock_box_session, mock_object_id)
+
+
 @pytest.fixture()
 def test_folder(mock_box_session, mock_object_id):
     return Folder(mock_box_session, mock_object_id)
diff --git a/test/unit/object/test_collaboration_whitelist.py b/test/unit/object/test_collaboration_whitelist.py
new file mode 100644
index 0000000..d5bd795
--- /dev/null
+++ b/test/unit/object/test_collaboration_whitelist.py
@@ -0,0 +1,100 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+import pytest
+
+from boxsdk.config import API
+from boxsdk.object.collaboration_whitelist_entry import CollaborationWhitelistEntry
+from boxsdk.object.collaboration_whitelist_exempt_target import CollaborationWhitelistExemptTarget
+
+
+def test_get_entries(mock_box_session, test_collaboration_whitelist):
+    expected_url = '{0}/collaboration_whitelist_entries'.format(API.BASE_API_URL)
+    mock_entry = {
+        'type': 'collaboration_whitelist_entry',
+        'id': '12345',
+        'domain': 'box.com',
+        'direction': 'both'
+    }
+    mock_box_session.get.return_value.json.return_value = {
+        'limit': 100,
+        'entries': [mock_entry]
+    }
+    entries = test_collaboration_whitelist.get_entries()
+    entry = entries.next()
+    mock_box_session.get.assert_called_once_with(expected_url, params={})
+    assert isinstance(entry, CollaborationWhitelistEntry)
+    assert entry.id == mock_entry['id']
+    assert entry.direction == mock_entry['direction']
+    assert entry.domain == mock_entry['domain']
+
+
+def test_get_exemptions(mock_box_session, test_collaboration_whitelist):
+    expected_url = '{0}/collaboration_whitelist_exempt_targets'.format(API.BASE_API_URL)
+    mock_exemption = {
+        'type': 'collaboration_whitelist_exempt_target',
+        'id': '12345',
+        'user': {
+            'type': 'user',
+            'id': '33333'
+        }
+    }
+    mock_box_session.get.return_value.json.return_value = {
+        'limit': 100,
+        'entries': [mock_exemption]
+    }
+    exemptions = test_collaboration_whitelist.get_exemptions()
+    exemption = exemptions.next()
+    mock_box_session.get.assert_called_once_with(expected_url, params={})
+    assert isinstance(exemption, CollaborationWhitelistExemptTarget)
+    assert exemption.id == mock_exemption['id']
+    assert exemption.user['id'] == mock_exemption['user']['id']
+
+
+@pytest.mark.parametrize(
+    'direction',
+    ['inbound', 'outbound', 'both']
+)
+def test_add_domain(mock_box_session, test_collaboration_whitelist, direction):
+    expected_url = '{0}/collaboration_whitelist_entries'.format(API.BASE_API_URL)
+    domain = 'example.com'
+    mock_entry = {
+        'type': 'collaboration_whitelist_entry',
+        'id': '12345',
+        'domain': domain,
+        'direction': direction
+    }
+    expected_data = {
+        'domain': domain,
+        'direction': direction
+    }
+    mock_box_session.post.return_value.json.return_value = mock_entry
+    entry = test_collaboration_whitelist.add_domain(domain, direction)
+    mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data))
+    assert isinstance(entry, CollaborationWhitelistEntry)
+    assert entry.id == mock_entry['id']
+    assert entry.domain == domain
+    assert entry.direction == direction
+
+
+def test_add_exemption(mock_box_session, test_collaboration_whitelist, mock_user):
+    expected_url = '{0}/collaboration_whitelist_exempt_targets'.format(API.BASE_API_URL)
+    expected_data = {
+        'user': {
+            'id': mock_user.object_id
+        }
+    }
+    mock_exemption = {
+        'type': 'collaboration_whitelist_exempt_target',
+        'id': '12345',
+        'user': {
+            'type': 'user',
+            'id': mock_user.object_id
+        }
+    }
+    mock_box_session.post.return_value.json.return_value = mock_exemption
+    exemption = test_collaboration_whitelist.add_exemption(mock_user)
+    mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_data))
+    assert exemption.id == mock_exemption['id']
+    assert exemption.user['id'] == mock_exemption['user']['id']
diff --git a/test/unit/object/test_collaboration_whitelist_entry.py b/test/unit/object/test_collaboration_whitelist_entry.py
new file mode 100644
index 0000000..0e3330e
--- /dev/null
+++ b/test/unit/object/test_collaboration_whitelist_entry.py
@@ -0,0 +1,28 @@
+# coding: utf-8
+from __future__ import unicode_literals, absolute_import
+
+from boxsdk.config import API
+
+
+def test_get(mock_box_session, test_collaboration_whitelist_entry):
+    entry_id = test_collaboration_whitelist_entry.object_id
+    expected_url = '{0}/collaboration_whitelist_entries/{1}'.format(API.BASE_API_URL, entry_id)
+    mock_entry = {
+        'type': 'collaboration_whitelist_entry',
+        'id': '98765',
+        'domain': 'example.com',
+        'direction': 'inbound'
+    }
+    mock_box_session.get.return_value.json.return_value = mock_entry
+    entry = test_collaboration_whitelist_entry.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert entry.id == mock_entry['id']
+    assert entry.domain == mock_entry['domain']
+    assert entry.direction == mock_entry['direction']
+
+
+def test_delete(mock_box_session, test_collaboration_whitelist_entry):
+    entry_id = test_collaboration_whitelist_entry.object_id
+    expected_url = '{0}/collaboration_whitelist_entries/{1}'.format(API.BASE_API_URL, entry_id)
+    test_collaboration_whitelist_entry.delete()
+    mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={})
diff --git a/test/unit/object/test_collaboration_whitelist_exempt_target.py b/test/unit/object/test_collaboration_whitelist_exempt_target.py
new file mode 100644
index 0000000..e0db97b
--- /dev/null
+++ b/test/unit/object/test_collaboration_whitelist_exempt_target.py
@@ -0,0 +1,29 @@
+
+# coding: utf-8
+from __future__ import unicode_literals, absolute_import
+
+from boxsdk.config import API
+
+
+def test_get(mock_box_session, test_collaboration_whitelist_exemption):
+    exemption_id = test_collaboration_whitelist_exemption.object_id
+    expected_url = '{0}/collaboration_whitelist_exempt_targets/{1}'.format(API.BASE_API_URL, exemption_id)
+    mock_exemption = {
+        'type': 'collaboration_whitelist_entry',
+        'id': '98765',
+        'domain': 'example.com',
+        'direction': 'inbound'
+    }
+    mock_box_session.get.return_value.json.return_value = mock_exemption
+    exemption = test_collaboration_whitelist_exemption.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert exemption.id == mock_exemption['id']
+    assert exemption.domain == mock_exemption['domain']
+    assert exemption.direction == mock_exemption['direction']
+
+
+def test_delete(mock_box_session, test_collaboration_whitelist_exemption):
+    exemption_id = test_collaboration_whitelist_exemption.object_id
+    expected_url = mock_box_session.get_url('collaboration_whitelist_exempt_targets', exemption_id)
+    test_collaboration_whitelist_exemption.delete()
+    mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={})