launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01381
[Merge] lp:~leonardr/launchpad/rename-grant-permissions into lp:launchpad/devel
Leonard Richardson has proposed merging lp:~leonardr/launchpad/rename-grant-permissions into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Earlier, I accidentally proposed this branch for merging into db-devel instead of devel. I make this mistake pretty much every time, and ordinarily I'd just delete the merge proposal and try again, but the old merge proposal acquired significant history before I noticed my mistake. So here's a new merge proposal, and the history is at https://code.edge.launchpad.net/~leonardr/launchpad/rename-grant-permissions/+merge/36363
--
https://code.launchpad.net/~leonardr/launchpad/rename-grant-permissions/+merge/37590
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~leonardr/launchpad/rename-grant-permissions into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/browser/oauth.py'
--- lib/canonical/launchpad/browser/oauth.py 2010-09-20 16:45:03 +0000
+++ lib/canonical/launchpad/browser/oauth.py 2010-10-05 11:52:47 +0000
@@ -16,6 +16,7 @@
Action,
Actions,
)
+from zope.security.interfaces import Unauthorized
from canonical.launchpad.interfaces.oauth import (
IOAuthConsumerSet,
@@ -88,11 +89,13 @@
token = consumer.newRequestToken()
if self.request.headers.get('Accept') == HTTPResource.JSON_TYPE:
- # Don't show the client the GRANT_PERMISSIONS access
+ # Don't show the client the DESKTOP_INTEGRATION access
# level. If they have a legitimate need to use it, they'll
# already know about it.
- permissions = [permission for permission in OAuthPermission.items
- if permission != OAuthPermission.GRANT_PERMISSIONS]
+ permissions = [
+ permission for permission in OAuthPermission.items
+ if (permission != OAuthPermission.DESKTOP_INTEGRATION)
+ ]
return self.getJSONRepresentation(
permissions, token, include_secret=True)
return u'oauth_token=%s&oauth_token_secret=%s' % (
@@ -102,26 +105,36 @@
return form.token is not None and not form.token.is_reviewed
+def token_review_success(form, action, data):
+ """The success callback for a button to approve a token."""
+ form.reviewToken(action.permission)
+
+
def create_oauth_permission_actions():
- """Return a list of `Action`s for each possible `OAuthPermission`."""
- actions = Actions()
- actions_excluding_grant_permissions = Actions()
- def success(form, action, data):
- form.reviewToken(action.permission)
+ """Return two `Actions` objects containing each possible `OAuthPermission`.
+
+ The first `Actions` object contains every action supported by the
+ OAuthAuthorizeTokenView. The second list contains a good default
+ set of actions, omitting special permissions like DESKTOP_INTEGRATION.
+ """
+ all_actions = Actions()
+ ordinary_actions = Actions()
for permission in OAuthPermission.items:
action = Action(
- permission.title, name=permission.name, success=success,
+ permission.title, name=permission.name,
+ success=token_review_success,
condition=token_exists_and_is_not_reviewed)
action.permission = permission
- actions.append(action)
- if permission != OAuthPermission.GRANT_PERMISSIONS:
- actions_excluding_grant_permissions.append(action)
- return actions, actions_excluding_grant_permissions
+ all_actions.append(action)
+ if permission != OAuthPermission.DESKTOP_INTEGRATION:
+ ordinary_actions.append(action)
+ return all_actions, ordinary_actions
+
class OAuthAuthorizeTokenView(LaunchpadFormView, JSONTokenMixin):
"""Where users authorize consumers to access Launchpad on their behalf."""
- actions, actions_excluding_grant_permissions = (
+ actions, actions_excluding_special_permissions = (
create_oauth_permission_actions())
label = "Authorize application to access Launchpad on your behalf"
schema = IOAuthRequestToken
@@ -130,7 +143,7 @@
@property
def visible_actions(self):
- """Restrict the actions to the subset the client can make use of.
+ """Restrict the actions to a subset to be presented to the client.
Not all client programs can function with all levels of
access. For instance, a client that needs to modify the
@@ -150,7 +163,7 @@
allowed_permissions = self.request.form_ng.getAll('allow_permission')
if len(allowed_permissions) == 0:
- return self.actions_excluding_grant_permissions
+ return self.actions_excluding_special_permissions
actions = Actions()
# UNAUTHORIZED is always one of the options. If the client
@@ -159,18 +172,59 @@
if OAuthPermission.UNAUTHORIZED.name in allowed_permissions:
allowed_permissions.remove(OAuthPermission.UNAUTHORIZED.name)
- # GRANT_PERMISSIONS cannot be requested as one of several
+ # DESKTOP_INTEGRATION cannot be requested as one of several
# options--it must be the only option (other than
- # UNAUTHORIZED). If GRANT_PERMISSIONS is one of several
+ # UNAUTHORIZED). If DESKTOP_INTEGRATION is one of several
# options, remove it from the list.
- if (OAuthPermission.GRANT_PERMISSIONS.name in allowed_permissions
+ desktop_permission = OAuthPermission.DESKTOP_INTEGRATION
+ if (desktop_permission.name in allowed_permissions
and len(allowed_permissions) > 1):
- allowed_permissions.remove(OAuthPermission.GRANT_PERMISSIONS.name)
-
- for action in self.actions:
- if (action.permission.name in allowed_permissions
- or action.permission is OAuthPermission.UNAUTHORIZED):
- actions.append(action)
+ allowed_permissions.remove(desktop_permission.name)
+
+ if desktop_permission.name in allowed_permissions:
+ if not self.token.consumer.is_integrated_desktop:
+ # Consumers may only ask for desktop integration if
+ # they give a desktop type (eg. "Ubuntu") and a
+ # user-recognizable desktop name (eg. the hostname).
+ raise Unauthorized(
+ ('Consumer "%s" asked for desktop integration, '
+ "but didn't say what kind of desktop it is, or name "
+ "the computer being integrated."
+ % self.token.consumer.key))
+
+ # We're going for desktop integration. The only two
+ # possibilities are "allow" and "deny". We'll customize
+ # the "allow" message using the hostname provided by the
+ # desktop.
+ #
+ # Since self.actions is a descriptor that returns copies
+ # of Action objects, we can modify the actions we get
+ # in-place without ruining the Action objects for everyone
+ # else.
+ desktop_name = self.token.consumer.integrated_desktop_name
+ label = (
+ 'Give all programs running on "%s" access '
+ 'to my Launchpad account.')
+ allow_action = [
+ action for action in self.actions
+ if action.name == desktop_permission.name][0]
+ allow_action.label = label % desktop_name
+ actions.append(allow_action)
+
+ # We'll customize the "deny" message as well.
+ label = "No, thanks, I don't trust "%s"."
+ deny_action = [
+ action for action in self.actions
+ if action.name == OAuthPermission.UNAUTHORIZED.name][0]
+ deny_action.label = label % desktop_name
+ actions.append(deny_action)
+
+ else:
+ # We're going for web-based integration.
+ for action in self.actions_excluding_special_permissions:
+ if (action.permission.name in allowed_permissions
+ or action.permission is OAuthPermission.UNAUTHORIZED):
+ actions.append(action)
if len(list(actions)) == 1:
# The only visible action is UNAUTHORIZED. That means the
@@ -179,8 +233,8 @@
# UNAUTHORIZED). Rather than present the end-user with an
# impossible situation where their only option is to deny
# access, we'll present the full range of actions (except
- # for GRANT_PERMISSIONS).
- return self.actions_excluding_grant_permissions
+ # for special permissions like DESKTOP_INTEGRATION).
+ return self.actions_excluding_special_permissions
return actions
def initialize(self):
@@ -189,6 +243,31 @@
key = form.get('oauth_token')
if key:
self.token = getUtility(IOAuthRequestTokenSet).getByKey(key)
+
+
+ callback = self.request.form.get('oauth_callback')
+ if (self.token is not None
+ and self.token.consumer.is_integrated_desktop):
+ # Nip problems in the bud by appling special rules about
+ # what desktop integrations are allowed to do.
+ if callback is not None:
+ # A desktop integration is not allowed to specify a callback.
+ raise Unauthorized(
+ "A desktop integration may not specify an "
+ "OAuth callback URL.")
+ # A desktop integration token can only have one of two
+ # permission levels: "Desktop Integration" and
+ # "Unauthorized". It shouldn't even be able to ask for any
+ # other level.
+ for action in self.visible_actions:
+ if action.permission not in (
+ OAuthPermission.DESKTOP_INTEGRATION,
+ OAuthPermission.UNAUTHORIZED):
+ raise Unauthorized(
+ ("Desktop integration token requested a permission "
+ '("%s") not supported for desktop-wide use.')
+ % action.label)
+
super(OAuthAuthorizeTokenView, self).initialize()
def render(self):
=== modified file 'lib/canonical/launchpad/database/oauth.py'
--- lib/canonical/launchpad/database/oauth.py 2010-09-20 16:45:03 +0000
+++ lib/canonical/launchpad/database/oauth.py 2010-10-05 11:52:47 +0000
@@ -15,6 +15,7 @@
timedelta,
)
+import re
import pytz
from sqlobject import (
BoolCol,
@@ -93,6 +94,7 @@
getStore = _get_store
+
class OAuthConsumer(OAuthBase):
"""See `IOAuthConsumer`."""
implements(IOAuthConsumer)
@@ -102,6 +104,51 @@
key = StringCol(notNull=True)
secret = StringCol(notNull=False, default='')
+ # This regular expression singles out a consumer key that
+ # represents any and all apps running on a specific computer
+ # (usually a desktop). For instance:
+ #
+ # System-wide: Ubuntu desktop (hostname1)
+ # - An Ubuntu desktop called "hostname1"
+ # System-wide: Windows desktop (Computer Name)
+ # - A Windows desktop called "Computer Name"
+ # System-wide: Mac OS desktop (hostname2)
+ # - A Macintosh desktop called "hostname2"
+ # System-wide Android phone (Bob's Phone)
+ # - An Android phone called "Bob's Phone"
+ integrated_desktop_re = re.compile("^System-wide: (.*) \(([^)]*)\)$")
+
+ def _integrated_desktop_match_group(self, position):
+ """Return information about a desktop integration token.
+
+ A convenience method that runs the desktop integration regular
+ expression against the consumer key.
+
+ :param position: The match group to return if the regular
+ expression matches.
+
+ :return: The value of one of the match groups, or None.
+ """
+ match = self.integrated_desktop_re.match(self.key)
+ if match is None:
+ return None
+ return match.groups()[position]
+
+ @property
+ def is_integrated_desktop(self):
+ """See `IOAuthConsumer`."""
+ return self.integrated_desktop_re.match(self.key) is not None
+
+ @property
+ def integrated_desktop_type(self):
+ """See `IOAuthConsumer`."""
+ return self._integrated_desktop_match_group(0)
+
+ @property
+ def integrated_desktop_name(self):
+ """See `IOAuthConsumer`."""
+ return self._integrated_desktop_match_group(1)
+
def newRequestToken(self):
"""See `IOAuthConsumer`."""
key, secret = create_token_key_and_secret(table=OAuthRequestToken)
=== modified file 'lib/canonical/launchpad/doc/oauth.txt'
--- lib/canonical/launchpad/doc/oauth.txt 2010-04-16 15:06:55 +0000
+++ lib/canonical/launchpad/doc/oauth.txt 2010-10-05 11:52:47 +0000
@@ -38,6 +38,45 @@
...
AssertionError: ...
+Desktop consumers
+=================
+
+In a web context, each application is represented by a unique consumer
+key. But a typical user sitting at a typical desktop (or other
+personal computer), using multiple desktop applications that integrate
+with Launchpad, is represented by a single consumer key. The user's
+session as a whole is a single "consumer", and the consumer key is
+expected to contain structured information: the type of system
+(usually the operating system plus the word "desktop") and a string
+that the end-user would recognize as identifying their computer.
+
+ >>> desktop_key = consumer_set.new(
+ ... "System-wide: Ubuntu desktop (hostname)")
+ >>> desktop_key.is_integrated_desktop
+ True
+ >>> print desktop_key.integrated_desktop_type
+ Ubuntu desktop
+ >>> print desktop_key.integrated_desktop_name
+ hostname
+
+ >>> desktop_key = consumer_set.new(
+ ... "System-wide: Android phone (My Phone)")
+ >>> desktop_key.is_integrated_desktop
+ True
+ >>> print desktop_key.integrated_desktop_type
+ Android phone
+ >>> print desktop_key.integrated_desktop_name
+ My Phone
+
+A normal OAuth consumer does not have this information.
+
+ >>> ordinary_key = consumer_set.new("Not a desktop at all.")
+ >>> ordinary_key.is_integrated_desktop
+ False
+ >>> print ordinary_key.integrated_desktop_type
+ None
+ >>> print ordinary_key.integrated_desktop_name
+ None
Request tokens
==============
=== modified file 'lib/canonical/launchpad/doc/webapp-authorization.txt'
--- lib/canonical/launchpad/doc/webapp-authorization.txt 2010-08-24 16:44:42 +0000
+++ lib/canonical/launchpad/doc/webapp-authorization.txt 2010-10-05 11:52:47 +0000
@@ -79,24 +79,16 @@
>>> check_permission('launchpad.View', bug_1)
False
-Now consider a principal authorized to create OAuth tokens. Whenever
-it's not creating OAuth tokens, it has a level of permission
-equivalent to READ_PUBLIC.
+A token used for desktop integration has a level of permission
+equivalent to WRITE_PUBLIC.
- >>> principal.access_level = AccessLevel.GRANT_PERMISSIONS
+ >>> principal.access_level = AccessLevel.DESKTOP_INTEGRATION
>>> setupInteraction(principal)
>>> check_permission('launchpad.View', bug_1)
- False
+ True
>>> check_permission('launchpad.Edit', sample_person)
- False
-
-This may seem useless from a security standpoint, since once a
-malicious client is authorized to create OAuth tokens, it can escalate
-its privileges at any time by creating a new token for itself. The
-security benefit is more subtle: by discouraging feature creep in
-clients that have this super-access level, we reduce the risk that a
-bug in a _trusted_ client will enable privilege escalation attacks.
+ True
Users logged in through the web application have full access, which
means they can read/change any object they have access to.
=== modified file 'lib/canonical/launchpad/interfaces/oauth.py'
--- lib/canonical/launchpad/interfaces/oauth.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/interfaces/oauth.py 2010-10-05 11:52:47 +0000
@@ -64,6 +64,23 @@
description=_('The secret which, if not empty, should be used by the '
'consumer to sign its requests.'))
+ is_integrated_desktop = Attribute(
+ """This attribute is true if the consumer corresponds to a
+ user account on a personal computer or similar device.""")
+
+ integrated_desktop_name = Attribute(
+ """If the consumer corresponds to a user account on a personal
+ computer or similar device, this is the self-reported name of
+ the computer. If the consumer is a specific web or desktop
+ application, this is None.""")
+
+ integrated_desktop_type = Attribute(
+ """If the consumer corresponds to a user account on a personal
+ computer or similar device, this is the self-reported type of
+ that computer (usually the operating system plus the word
+ "desktop"). If the consumer is a specific web or desktop
+ application, this is None.""")
+
def newRequestToken():
"""Return a new `IOAuthRequestToken` with a random key and secret.
=== modified file 'lib/canonical/launchpad/pagetests/oauth/authorize-token.txt'
--- lib/canonical/launchpad/pagetests/oauth/authorize-token.txt 2010-09-20 16:45:03 +0000
+++ lib/canonical/launchpad/pagetests/oauth/authorize-token.txt 2010-10-05 11:52:47 +0000
@@ -45,7 +45,7 @@
See all applications authorized to access Launchpad on your behalf.
This page contains one submit button for each item of OAuthPermission,
-except for 'Grant Permissions', which must be specifically requested.
+except for 'Desktop Integration', which must be specifically requested.
>>> browser.getControl('No Access')
<SubmitControl...
@@ -58,6 +58,7 @@
>>> browser.getControl('Change Anything')
<SubmitControl...
+ # XXX FIXME
>>> browser.getControl('Grant Permissions')
Traceback (most recent call last):
...
@@ -74,11 +75,14 @@
that isn't enough for the application. The user always has the option
to deny permission altogether.
- >>> def print_access_levels(allow_permission):
+ >>> def authorize_token_main_content(allow_permission):
... browser.open(
... "http://launchpad.dev/+authorize-token?%s&%s"
... % (urlencode(params), allow_permission))
- ... main_content = find_tag_by_id(browser.contents, 'maincontent')
+ ... return find_tag_by_id(browser.contents, 'maincontent')
+
+ >>> def print_access_levels(allow_permission):
+ ... main_content = authorize_token_main_content(allow_permission)
... actions = main_content.findAll('input', attrs={'type': 'submit'})
... for action in actions:
... print action['value']
@@ -89,28 +93,9 @@
Change Non-Private Data
Change Anything
-The only time the 'Grant Permissions' permission shows up in this list
-is if the client specifically requests it, and no other
-permission. (Also requesting UNAUTHORIZED is okay--it will show up
-anyway.)
-
- >>> print_access_levels('allow_permission=GRANT_PERMISSIONS')
- No Access
- Grant Permissions
-
- >>> print_access_levels(
- ... 'allow_permission=GRANT_PERMISSIONS&allow_permission=UNAUTHORIZED')
- No Access
- Grant Permissions
-
- >>> print_access_levels(
- ... 'allow_permission=WRITE_PUBLIC&allow_permission=GRANT_PERMISSIONS')
- No Access
- Change Non-Private Data
-
If an application doesn't specify any valid access levels, or only
specifies the UNAUTHORIZED access level, Launchpad will show all the
-access levels, except for GRANT_PERMISSIONS.
+access levels, except for DESKTOP_INTEGRATION.
>>> print_access_levels('')
No Access
@@ -126,6 +111,20 @@
Read Anything
Change Anything
+An application may not request the DESKTOP_INTEGRATION access level
+unless its consumer key matches a certain pattern. (Successful desktop
+integration has its own section, below.)
+
+ >>> allow_permission = "allow_permission=DESKTOP_INTEGRATION"
+ >>> browser.open(
+ ... "http://launchpad.dev/+authorize-token?%s&%s"
+ ... % (urlencode(params), allow_permission))
+ Traceback (most recent call last):
+ ...
+ Unauthorized: Consumer "foobar123451432" asked for desktop
+ integration, but didn't say what kind of desktop it is, or name
+ the computer being integrated.
+
An application may also specify a context, so that the access granted
by the user is restricted to things related to that context.
@@ -263,3 +262,94 @@
This request for accessing Launchpad on your behalf has been
reviewed ... ago.
See all applications authorized to access Launchpad on your behalf.
+
+Desktop integration
+===================
+
+The test case given above shows how to integrate a single application
+or website into Launchpad. But it's also possible to integrate an
+entire desktop environment into Launchpad.
+
+The desktop integration option is only available for OAuth consumers
+that say what kind of desktop they are (eg. Ubuntu) and give a name
+that a user can identify with their computer (eg. the hostname). Here,
+we'll create such a token.
+
+ >>> login('salgado@xxxxxxxxxx')
+ >>> desktop_key = "System-wide: Ubuntu desktop (mycomputer)"
+ >>> consumer = getUtility(IOAuthConsumerSet).new(desktop_key)
+ >>> token = consumer.newRequestToken()
+ >>> logout()
+
+When a desktop tries to integrate with Launchpad, the user gets a
+special warning about giving access to every program running on their
+desktop.
+
+ >>> params = dict(oauth_token=token.key)
+ >>> print extract_text(
+ ... authorize_token_main_content(
+ ... 'allow_permission=DESKTOP_INTEGRATION'))
+ The Ubuntu desktop called mycomputer wants access to your
+ Launchpad account. If you allow the integration, all applications
+ running on mycomputer will have read-write access to your
+ Launchpad account, including to your private data.
+ If you're using a public computer, if mycomputer is not the
+ computer you're using right now, or if something just doesn't feel
+ right about this situation, you should click "No, thanks, I don't
+ trust 'mycomputer'", or close this window now. You can always try
+ again later.
+ Even if you decide to allow the integration, you can change your
+ mind later.
+ See all applications authorized to access Launchpad on your behalf.
+
+
+The only time the 'Desktop Integration' permission shows up in the
+list of permissions is if the client specifically requests it, and no
+other permission. (Also requesting UNAUTHORIZED is okay--it will show
+up anyway.)
+
+ >>> print_access_levels('allow_permission=DESKTOP_INTEGRATION')
+ Give all programs running on "mycomputer" access to my Launchpad account.
+ No, thanks, I don't trust "mycomputer".
+
+ >>> print_access_levels(
+ ... 'allow_permission=DESKTOP_INTEGRATION&allow_permission=UNAUTHORIZED')
+ Give all programs running on "mycomputer" access to my Launchpad account.
+ No, thanks, I don't trust "mycomputer".
+
+A desktop may not request a level of access other than
+DESKTOP_INTEGRATION, since the whole point is to have a permission
+level that specifically applies across the entire desktop.
+
+ >>> print_access_levels('allow_permission=WRITE_PRIVATE')
+ Traceback (most recent call last):
+ ...
+ Unauthorized: Desktop integration token requested a permission
+ ("Change Anything") not supported for desktop-wide use.
+
+ >>> print_access_levels(
+ ... 'allow_permission=WRITE_PUBLIC&allow_permission=DESKTOP_INTEGRATION')
+ Traceback (most recent call last):
+ ...
+ Unauthorized: Desktop integration token requested a permission
+ ("Change Non-Private Data") not supported for desktop-wide use.
+
+You can't specify a callback URL when authorizing a desktop-wide
+token, since callback URLs should only be used when integrating
+websites into Launchpad.
+
+ >>> params['oauth_callback'] = 'http://launchpad.dev/bzr'
+ >>> print_access_levels('allow_permission=DESKTOP_INTEGRATION')
+ Traceback (most recent call last):
+ ...
+ Unauthorized: A desktop integration may not specify an OAuth
+ callback URL.
+
+This is true even if the desktop token isn't asking for the
+DESKTOP_INTEGRATION permission.
+
+ >>> print_access_levels('allow_permission=WRITE_PRIVATE')
+ Traceback (most recent call last):
+ ...
+ Unauthorized: A desktop integration may not specify an OAuth
+ callback URL.
=== modified file 'lib/canonical/launchpad/templates/oauth-authorize.pt'
--- lib/canonical/launchpad/templates/oauth-authorize.pt 2009-07-17 17:59:07 +0000
+++ lib/canonical/launchpad/templates/oauth-authorize.pt 2010-10-05 11:52:47 +0000
@@ -21,28 +21,61 @@
<tal:token-not-reviewed condition="not:token/is_reviewed">
<div metal:use-macro="context/@@launchpad_form/form">
<div metal:fill-slot="extra_top">
- <p>The application identified as
- <strong tal:content="token/consumer/key">consumer</strong>
- wants to access
- <tal:has-context condition="view/token_context">
- things related to
- <strong tal:content="view/token_context/title">Context</strong>
- in
- </tal:has-context>
- Launchpad on your behalf. What level of access
- do you want to grant?</p>
-
- <table>
- <tr tal:repeat="action view/visible_actions">
- <td style="text-align: right">
- <tal:action replace="structure action/render" />
- </td>
- <td>
- <span class="lesser"
- tal:content="action/permission/description" />
- </td>
- </tr>
- </table>
+
+ <tal:desktop-integration-token condition="token/consumer/is_integrated_desktop">
+ <p>The
+ <tal:desktop replace="structure
+ token/consumer/integrated_desktop_type" />
+ called
+ <strong tal:content="token/consumer/integrated_desktop_name">hostname</strong>
+ wants access to your Launchpad account. If you allow the
+ integration, all applications running
+ on <strong tal:content="token/consumer/integrated_desktop_name">hostname</strong>
+ will have read-write access to your Launchpad account,
+ including to your private data.</p>
+
+ <p>If you're using a public computer, if
+ <strong tal:content="token/consumer/integrated_desktop_name">hostname</strong>
+ is not the computer you're using right now, or if
+ something just doesn't feel right about this situation,
+ you should click "No, thanks, I don't trust
+ '<tal:hostname replace="structure
+ token/consumer/integrated_desktop_name" />'",
+ or close this window now. You can always try
+ again later.</p>
+
+ <p>Even if you decide to allow the integration, you can
+ change your mind later.</p>
+ </tal:desktop-integration-token>
+
+ <tal:web-integration-token condition="not:token/consumer/is_integrated_desktop">
+ <p>The application identified as
+ <strong tal:content="token/consumer/key">consumer</strong>
+ wants to access
+ <tal:has-context condition="view/token_context">
+ things related to
+ <strong tal:content="view/token_context/title">Context</strong>
+ in
+ </tal:has-context>
+ Launchpad on your behalf. What level of access
+ do you want to grant?</p>
+ </tal:web-integration-token>
+
+ <table>
+ <tr tal:repeat="action view/visible_actions">
+ <td style="text-align: right">
+ <tal:action replace="structure action/render" />
+ </td>
+
+ <tal:web-integration-token
+ condition="not:token/consumer/is_integrated_desktop">
+ <td>
+ <span class="lesser"
+ tal:content="action/permission/description" />
+ </td>
+ </tal:web-integration-token>
+ </tr>
+ </table>
</div>
<div metal:fill-slot="extra_bottom">
=== modified file 'lib/canonical/launchpad/webapp/authorization.py'
--- lib/canonical/launchpad/webapp/authorization.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/webapp/authorization.py 2010-10-05 11:52:47 +0000
@@ -61,7 +61,8 @@
lp_permission = getUtility(ILaunchpadPermission, permission)
if lp_permission.access_level == "write":
required_access_level = [
- AccessLevel.WRITE_PUBLIC, AccessLevel.WRITE_PRIVATE]
+ AccessLevel.WRITE_PUBLIC, AccessLevel.WRITE_PRIVATE,
+ AccessLevel.DESKTOP_INTEGRATION]
if access_level not in required_access_level:
return False
elif lp_permission.access_level == "read":
@@ -80,7 +81,8 @@
access to private objects, return False. Return True otherwise.
"""
private_access_levels = [
- AccessLevel.READ_PRIVATE, AccessLevel.WRITE_PRIVATE]
+ AccessLevel.READ_PRIVATE, AccessLevel.WRITE_PRIVATE,
+ AccessLevel.DESKTOP_INTEGRATION]
if access_level in private_access_levels:
# The user has access to private objects. Return early,
# before checking whether the object is private, since
=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py 2010-09-12 11:43:36 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py 2010-10-05 11:52:47 +0000
@@ -527,14 +527,13 @@
for reading and changing anything, including private data.
""")
- GRANT_PERMISSIONS = DBItem(60, """
- Grant Permissions
+ DESKTOP_INTEGRATION = DBItem(60, """
+ Desktop Integration
- The application will be able to grant access to your Launchpad
- account to any other application. This is a very powerful
- level of access. You should not grant this level of access to
- any application except the official Launchpad credential
- manager.
+ Every application running on your desktop will have read-write
+ access to your Launchpad account, including to your private
+ data. You should not allow this unless you trust the computer
+ you're using right now.
""")
class AccessLevel(DBEnumeratedType):