launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06549
[Merge] lp:~wallyworld/launchpad/more-accesspolicy-service-3 into lp:launchpad
Ian Booth has proposed merging lp:~wallyworld/launchpad/more-accesspolicy-service-3 into lp:launchpad with lp:~wallyworld/launchpad/more-accesspolicy-service-2 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/more-accesspolicy-service-3/+merge/95168
== Implementation ==
Part 3 of the work to implement the +sharing page
Provide service implementation for getPillarObservers() and addPillarObserver() methods using the new access policy model code.
The +sharing page now works in that the sharing picker can be used to grant access. When the page is first opened, any existing observers are displayed.
Also, rename the product methods to pillar.
TODO:
- add deletePillarObserver implementation
- add security permissions (atm, anyone can see and amend the data)
== Tests ==
Add new test case: TestAccessPolicyService
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/registry/browser/product.py
lib/lp/registry/browser/tests/test_product.py
lib/lp/registry/interfaces/accesspolicyservice.py
lib/lp/registry/javascript/disclosure/pillar_sharing_view.js
lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.html
lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.js
lib/lp/registry/services/accesspolicyservice.py
lib/lp/registry/services/tests/test_accesspolicyservice.py
lib/lp/registry/templates/product-sharing.pt
--
https://code.launchpad.net/~wallyworld/launchpad/more-accesspolicy-service-3/+merge/95168
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/more-accesspolicy-service-3 into lp:launchpad.
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/browser/product.py 2012-02-29 13:21:20 +0000
@@ -2461,7 +2461,7 @@
@property
def observer_data(self):
- return self._getAccessPolicyService().getProductObservers(self.context)
+ return self._getAccessPolicyService().getPillarObservers(self.context)
def initialize(self):
super(ProductSharingView, self).initialize()
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2012-02-29 13:21:20 +0000
@@ -433,5 +433,5 @@
self.assertIsNotNone(cache.objects.get('access_policies'))
self.assertIsNotNone(cache.objects.get('sharing_permissions'))
aps = getUtility(IService, 'accesspolicy')
- observers = aps.getProductObservers(product)
+ observers = aps.getPillarObservers(product)
self.assertTrue(observers, cache.objects.get('observer_data'))
=== modified file 'lib/lp/registry/interfaces/accesspolicyservice.py'
--- lib/lp/registry/interfaces/accesspolicyservice.py 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/interfaces/accesspolicyservice.py 2012-02-29 13:21:20 +0000
@@ -13,22 +13,21 @@
from zope.schema import Choice
from lazr.restful.declarations import (
+ call_with,
export_as_webservice_entry,
export_read_operation,
export_write_operation,
operation_for_version,
operation_parameters,
+ REQUEST_USER,
)
from lazr.restful.fields import Reference
from lp import _
from lp.app.interfaces.services import IService
-from lp.registry.enums import (
- AccessPolicyType,
- SharingPermission,
- )
+from lp.registry.enums import AccessPolicyType
from lp.registry.interfaces.person import IPerson
-from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.pillar import IPillar
class IAccessPolicyService(IService):
@@ -46,26 +45,32 @@
@export_read_operation()
@operation_parameters(
- product=Reference(IProduct, title=_('Product'), required=True))
- @operation_for_version('devel')
- def getProductObservers(product):
- """Return people/teams who can see product artifacts."""
-
- @export_write_operation()
- @operation_parameters(
- product=Reference(IProduct, title=_('Product'), required=True),
- observer=Reference(IPerson, title=_('Observer'), required=True),
- access_policy=Choice(vocabulary=AccessPolicyType),
- sharing_permission=Choice(vocabulary=SharingPermission))
- @operation_for_version('devel')
- def addProductObserver(product, observer, access_policy,
- sharing_permission):
- """Add an observer with the access policy to a product."""
-
- @export_write_operation()
- @operation_parameters(
- product=Reference(IProduct, title=_('Product'), required=True),
- observer=Reference(IPerson, title=_('Observer'), required=True))
- @operation_for_version('devel')
- def deleteProductObserver(product, observer):
- """Remove an observer from a product."""
+ pillar=Reference(IPillar, title=_('Pillar'), required=True))
+ @operation_for_version('devel')
+ def getPillarObservers(pillar):
+ """Return people/teams who can see pillar artifacts."""
+
+ @export_write_operation()
+ @call_with(user=REQUEST_USER)
+ @operation_parameters(
+ pillar=Reference(IPillar, title=_('Pillar'), required=True),
+ observer=Reference(IPerson, title=_('Observer'), required=True),
+ access_policy_type=Choice(vocabulary=AccessPolicyType))
+ @operation_for_version('devel')
+ def addPillarObserver(pillar, observer, access_policy_type, user):
+ """Add an observer with the access policy to a pillar."""
+
+ @export_write_operation()
+ @operation_parameters(
+ pillar=Reference(IPillar, title=_('Pillar'), required=True),
+ observer=Reference(IPerson, title=_('Observer'), required=True),
+ access_policy_type=Choice(vocabulary=AccessPolicyType))
+ @operation_for_version('devel')
+ def deletePillarObserver(pillar, observer, access_policy_type):
+ """Remove an observer from a pillar.
+
+ :param pillar: the pillar from which to remove access
+ :param observer: the person or team to remove
+ :param access_policy_type: if None, remove all access, otherwise just
+ remove the specified access_policy
+ """
=== renamed file 'lib/lp/registry/javascript/disclosure/product_sharing.js' => 'lib/lp/registry/javascript/disclosure/pillar_sharing_view.js'
--- lib/lp/registry/javascript/disclosure/product_sharing.js 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/javascript/disclosure/pillar_sharing_view.js 2012-02-29 13:21:20 +0000
@@ -10,11 +10,11 @@
var namespace = Y.namespace('lp.registry.disclosure.sharing');
-function ProductSharingView(config) {
- ProductSharingView.superclass.constructor.apply(this, arguments);
+function PillarSharingView(config) {
+ PillarSharingView.superclass.constructor.apply(this, arguments);
}
-ProductSharingView.ATTRS = {
+PillarSharingView.ATTRS = {
lp_client: {
value: new Y.lp.client.Launchpad()
},
@@ -28,7 +28,7 @@
}
};
-Y.extend(ProductSharingView, Y.Widget, {
+Y.extend(PillarSharingView, Y.Widget, {
initializer: function(config) {
var vocab = 'ValidPillarOwner';
@@ -155,7 +155,7 @@
*/
performRemoveObserver: function(delete_link, person_uri) {
var error_handler = new Y.lp.client.ErrorHandler();
- var product_uri = LP.cache.context.self_link;
+ var pillar_uri = LP.cache.context.self_link;
var self = this;
var y_config = {
on: {
@@ -167,12 +167,12 @@
failure: error_handler.getFailureHandler()
},
parameters: {
- product: product_uri,
+ pillar: pillar_uri,
observer: person_uri
}
};
this.get('lp_client').named_post(
- '/+services/accesspolicy', 'deleteProductObserver', y_config);
+ '/+services/accesspolicy', 'deletePillarObserver', y_config);
},
/**
@@ -219,7 +219,7 @@
*/
saveSharingSelection: function(selection_result) {
var error_handler = new Y.lp.client.ErrorHandler();
- var product_uri = LP.cache.context.self_link;
+ var pillar_uri = LP.cache.context.self_link;
var person_uri = Y.lp.client.normalize_uri(selection_result.api_uri);
person_uri = Y.lp.client.get_absolute_uri(person_uri);
var self = this;
@@ -233,19 +233,18 @@
failure: error_handler.getFailureHandler()
},
parameters: {
- product: product_uri,
+ pillar: pillar_uri,
observer: person_uri,
- access_policy: selection_result.access_policy,
- sharing_permission: 'All'
+ access_policy_type: selection_result.access_policy
}
};
this.get('lp_client').named_post(
- '/+services/accesspolicy', 'addProductObserver', y_config);
+ '/+services/accesspolicy', 'addPillarObserver', y_config);
}
});
-ProductSharingView.NAME = 'productSharingView';
-namespace.ProductSharingView = ProductSharingView;
+PillarSharingView.NAME = 'pillarSharingView';
+namespace.PillarSharingView = PillarSharingView;
}, "0.1", { "requires": [
'node', 'lp.client', 'lp.mustache', 'lazr.picker', 'lp.app.picker',
=== renamed file 'lib/lp/registry/javascript/disclosure/tests/test_product_sharing_view.html' => 'lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.html'
--- lib/lp/registry/javascript/disclosure/tests/test_product_sharing_view.html 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.html 2012-02-29 13:21:20 +0000
@@ -7,7 +7,7 @@
<html>
<head>
- <title>Product Sharing View Tests</title>
+ <title>Pillar Sharing View Tests</title>
<!-- YUI and test setup -->
<script type="text/javascript"
@@ -62,10 +62,10 @@
src="../../../../../../build/js/lp/registry/disclosure/disclosure_person_picker.js"></script>
<!-- The module under test. -->
- <script type="text/javascript" src="../product_sharing.js"></script>
+ <script type="text/javascript" src="../pillar_sharing_view.js"></script>
<!-- The test suite -->
- <script type="text/javascript" src="test_product_sharing_view.js"></script>
+ <script type="text/javascript" src="test_pillar_sharing_view.js"></script>
<script id="test-fixture" type="text/x-template">
<table id='observer-table'></table>
=== renamed file 'lib/lp/registry/javascript/disclosure/tests/test_product_sharing_view.js' => 'lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.js'
--- lib/lp/registry/javascript/disclosure/tests/test_product_sharing_view.js 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/javascript/disclosure/tests/test_pillar_sharing_view.js 2012-02-29 13:21:20 +0000
@@ -15,7 +15,7 @@
window.LP = {
links: {},
cache: {
- context: {self_link: "~product" },
+ context: {self_link: "~pillar" },
observer_data: [
{'name': 'fred', 'display_name': 'Fred Bloggs',
'role': '(Maintainer)', web_link: '~fred',
@@ -50,7 +50,7 @@
_create_Widget: function(cfg) {
var ns = Y.lp.registry.disclosure.sharing;
- return new ns.ProductSharingView(cfg);
+ return new ns.PillarSharingView(cfg);
},
test_library_exists: function () {
@@ -62,7 +62,7 @@
test_widget_can_be_instantiated: function() {
this.view = this._create_Widget();
Y.Assert.isInstanceOf(
- Y.lp.registry.disclosure.sharing.ProductSharingView,
+ Y.lp.registry.disclosure.sharing.PillarSharingView,
this.view,
"Observer table failed to be instantiated");
},
@@ -131,7 +131,7 @@
'/api/devel/+services/accesspolicy',
mockio.last_request.url);
Y.Assert.areEqual(
- 'ws.op=deleteProductObserver&product=~product' +
+ 'ws.op=deletePillarObserver&pillar=~pillar' +
'&observer=~fred',
mockio.last_request.config.data);
mockio.last_request.successJSON({});
@@ -197,15 +197,13 @@
person_uri = Y.lp.client.get_absolute_uri(person_uri);
var expected_url;
expected_url = Y.lp.client.append_qs(
- expected_url, 'ws.op', 'addProductObserver');
+ expected_url, 'ws.op', 'addPillarObserver');
expected_url = Y.lp.client.append_qs(
- expected_url, 'product', '~product');
+ expected_url, 'pillar', '~pillar');
expected_url = Y.lp.client.append_qs(
expected_url, 'observer', person_uri);
expected_url = Y.lp.client.append_qs(
- expected_url, 'access_policy', 'Policy 2');
- expected_url = Y.lp.client.append_qs(
- expected_url, 'sharing_permission', 'All');
+ expected_url, 'access_policy_type', 'Policy 2');
Y.Assert.areEqual(expected_url, mockio.last_request.config.data);
mockio.last_request.successJSON({
'resource_type_link': 'entity',
=== modified file 'lib/lp/registry/services/accesspolicyservice.py'
--- lib/lp/registry/services/accesspolicyservice.py 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/services/accesspolicyservice.py 2012-02-29 13:21:20 +0000
@@ -18,8 +18,11 @@
AccessPolicyType,
SharingPermission,
)
+from lp.registry.interfaces.accesspolicy import (
+ IAccessPolicySource,
+ IAccessPolicyGrantSource,
+ )
from lp.registry.interfaces.accesspolicyservice import IAccessPolicyService
-from lp.registry.interfaces.person import IPersonSet
class AccessPolicyService:
@@ -61,40 +64,59 @@
sharing_permissions.append(item)
return sharing_permissions
- def getProductObservers(self, product):
+ def getPillarObservers(self, pillar):
"""See `IAccessPolicyService`."""
- # TODO - replace this sample data with something real
+
+ # Currently support querying for sharing_permission = ALL
+ # TODO - support querying for sharing_permission = SOME
+
+ policies = getUtility(IAccessPolicySource).findByPillar([pillar])
+ policy_grant_source = getUtility(IAccessPolicyGrantSource)
+ grants = policy_grant_source.findByPolicy(policies)
+
result = []
+ person_by_id = {}
request = get_current_web_service_request()
- personset = getUtility(IPersonSet)
- for id in range(1, 4):
- person = personset.get(id)
- resource = EntryResource(person, request)
- person_data = resource.toDataForJSON()
- permissions = {
- 'PUBLICSECURITY': 'SOME',
- 'EMBARGOEDSECURITY': 'ALL'
- }
- if id > 2:
- permissions['USERDATA'] = 'SOME'
- person_data['permissions'] = permissions
+ for g in grants:
+ if not g.grantee.id in person_by_id:
+ resource = EntryResource(g.grantee, request)
+ person_data = resource.toDataForJSON()
+ person_data['permissions'] = {}
+ person_by_id[g.grantee.id] = person_data
+ person_data = person_by_id[g.grantee.id]
+ person_data['permissions'][g.policy.type.name] = (
+ SharingPermission.ALL.name)
result.append(person_data)
return result
- def addProductObserver(self, product, observer, access_policy,
- sharing_permission):
+ def addPillarObserver(self, pillar, observer, access_policy_type, user):
"""See `IAccessPolicyService`."""
- # TODO - implement this
+
+ # Create a pillar access policy if one doesn't exist.
+ policy_source = getUtility(IAccessPolicySource)
+ pillar_access_policy = [(pillar, access_policy_type)]
+ policies = list(policy_source.find(pillar_access_policy))
+ if len(policies) == 0:
+ [policy] = policy_source.create(pillar_access_policy)
+ else:
+ policy = policies[0]
+ # We have a policy, create the grant if it doesn't exist.
+ policy_grant_source = getUtility(IAccessPolicyGrantSource)
+ grants = list(policy_grant_source.find([(policy, observer)]))
+ if len(grants) == 0:
+ policy_grant_source.grant([(policy, observer, user)])
+
+ # Return observer data to the caller.
request = get_current_web_service_request()
resource = EntryResource(observer, request)
person_data = resource.toDataForJSON()
permissions = {
- access_policy.name: sharing_permission.name,
+ access_policy_type.name: SharingPermission.ALL.name,
}
person_data['permissions'] = permissions
return person_data
- def deleteProductObserver(self, product, observer):
+ def deletePillarObserver(self, pillar, observer, access_policy_type):
"""See `IAccessPolicyService`."""
# TODO - implement this
pass
=== modified file 'lib/lp/registry/services/tests/test_accesspolicyservice.py'
--- lib/lp/registry/services/tests/test_accesspolicyservice.py 2012-02-28 04:24:19 +0000
+++ lib/lp/registry/services/tests/test_accesspolicyservice.py 2012-02-29 13:21:20 +0000
@@ -5,9 +5,16 @@
import simplejson
+from lazr.restful import EntryResource
+from lazr.restful.utils import get_current_web_service_request
from zope.component import getUtility
-from lp.registry.enums import AccessPolicyType
+from lp.app.interfaces.services import IService
+from lp.registry.enums import AccessPolicyType, SharingPermission
+from lp.registry.interfaces.accesspolicy import (
+ IAccessPolicyGrantSource,
+ IAccessPolicySource,
+ )
from lp.registry.services.accesspolicyservice import AccessPolicyService
from lp.services.webapp.interfaces import ILaunchpadRoot
from lp.services.webapp.publisher import canonical_url
@@ -15,10 +22,70 @@
TestCaseWithFactory,
WebServiceTestCase,
)
-from lp.testing.layers import AppServerLayer
+from lp.testing.layers import AppServerLayer, DatabaseFunctionalLayer
from lp.testing.pages import LaunchpadWebServiceCaller
+class TestAccessPolicyService(TestCaseWithFactory):
+ """Tests for the AccessPolicyService."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestAccessPolicyService, self).setUp()
+ self.service = getUtility(IService, 'accesspolicy')
+
+ def _makeObserverData(self, observer):
+ # Unpack an observer into it's attributes and add in permissions.
+ request = get_current_web_service_request()
+ resource = EntryResource(observer, request)
+ observer_data = resource.toDataForJSON()
+ observer_data['permissions'] = {
+ AccessPolicyType.PROPRIETARY.name: SharingPermission.ALL.name}
+ return observer_data
+
+ def _test_getPillarObservers(self, pillar):
+ """getPillarObservers returns the expected data."""
+ access_policy = self.factory.makeAccessPolicy(pillar=pillar)
+ grantee = self.factory.makePerson()
+ self.factory.makeAccessPolicyGrant(access_policy, grantee)
+ [observer] = self.service.getPillarObservers(pillar)
+ person_data = self._makeObserverData(grantee)
+ self.assertContentEqual(person_data, observer)
+
+ def test_getProductObservers(self):
+ product = self.factory.makeProduct()
+ self._test_getPillarObservers(product)
+
+ def test_getDistroObservers(self):
+ distro = self.factory.makeDistribution()
+ self._test_getPillarObservers(distro)
+
+ def _test_addPillarObserver(self, pillar):
+ """addPillarObservers works and returns the expected data."""
+ observer = self.factory.makePerson()
+ access_policy_type = AccessPolicyType.USERDATA
+ user = self.factory.makePerson()
+ observer_data = self.service.addPillarObserver(
+ pillar, observer, access_policy_type, user)
+ [policy] = getUtility(IAccessPolicySource).findByPillar([pillar])
+ policy_grant_source = getUtility(IAccessPolicyGrantSource)
+ [grant] = policy_grant_source.findByPolicy([policy])
+ self.assertEqual(user, grant.grantor)
+ self.assertEqual(observer, grant.grantee)
+ self.assertEqual(policy, grant.policy)
+ expected_observer_data = self._makeObserverData(observer)
+ self.assertContentEqual(expected_observer_data, observer_data)
+
+ def test_addProductObserver(self):
+ product = self.factory.makeProduct()
+ self._test_addPillarObserver(product)
+
+ def test_addDistroObserver(self):
+ distro = self.factory.makeDistribution()
+ self._test_addPillarObserver(distro)
+
+
class ApiTestMixin:
"""Common tests for launchpadlib and webservice."""
=== modified file 'lib/lp/registry/templates/product-sharing.pt'
--- lib/lp/registry/templates/product-sharing.pt 2012-02-29 13:21:19 +0000
+++ lib/lp/registry/templates/product-sharing.pt 2012-02-29 13:21:20 +0000
@@ -15,7 +15,7 @@
function(Y) {
Y.on('domready', function() {
var config = ${view/json_sharing_picker_config}
- var view_widget = new Y.lp.registry.disclosure.sharing.ProductSharingView(config);
+ var view_widget = new Y.lp.registry.disclosure.sharing.PillarSharingView(config);
view_widget.render();
});
});
@@ -30,17 +30,6 @@
</p>
<table id="observer-table" class="disclosure listing">
- <thead>
- <tr>
- <th colspan="2" style="width: 33%">User or Team</th>
- <th colspan="2" style="width: ">Sharing <span class="help">
- (<a href="/+help-registry/sharing.html"
- target="help" class="js-action help">help</a>)</span>
- </th>
- <th colspan="1" style="width: ">Shared items</th>
- </tr>
- </thead>
- <tbody id="observer-table-body"></tbody>
</table>
</div>