Merge "Add multiple reseller prefixes and composite tokens"
This commit is contained in:
commit
d6467d3385
@ -30,6 +30,10 @@ following actions occur:
|
|||||||
The account owner can grant account and container access to users
|
The account owner can grant account and container access to users
|
||||||
through access control lists (ACLs).
|
through access control lists (ACLs).
|
||||||
|
|
||||||
|
In addition, it is possible to provide an additional token in the
|
||||||
|
''X-Service-Token'' header. More information about how this is used is in
|
||||||
|
:doc:`../overview_backing_store`.
|
||||||
|
|
||||||
The following list describes the authentication services that you can
|
The following list describes the authentication services that you can
|
||||||
use with Object Storage:
|
use with Object Storage:
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ Overview and Concepts
|
|||||||
overview_expiring_objects
|
overview_expiring_objects
|
||||||
cors
|
cors
|
||||||
crossdomain
|
crossdomain
|
||||||
|
overview_backing_store
|
||||||
associated_projects
|
associated_projects
|
||||||
|
|
||||||
Developer Documentation
|
Developer Documentation
|
||||||
|
@ -193,12 +193,87 @@ your situation, but in short:
|
|||||||
:ref:`staticweb`, :ref:`formpost`, :ref:`tempurl`, and authenticated
|
:ref:`staticweb`, :ref:`formpost`, :ref:`tempurl`, and authenticated
|
||||||
capabilities requests (using :ref:`discoverability`).
|
capabilities requests (using :ref:`discoverability`).
|
||||||
|
|
||||||
and you can finally add the keystoneauth configuration::
|
and you can finally add the keystoneauth configuration. Here is a simple
|
||||||
|
configuration::
|
||||||
|
|
||||||
[filter:keystoneauth]
|
[filter:keystoneauth]
|
||||||
use = egg:swift#keystoneauth
|
use = egg:swift#keystoneauth
|
||||||
operator_roles = admin, swiftoperator
|
operator_roles = admin, swiftoperator
|
||||||
|
|
||||||
|
Use an appropriate list of roles in operator_roles. For example, in
|
||||||
|
some systems, the role ``_member_`` or ``Member`` is used to indicate
|
||||||
|
that the user is allowed to operate on project resources.
|
||||||
|
|
||||||
|
OpenStack Service Using Composite Tokens
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Some Openstack services such as Cinder and Glance may use
|
||||||
|
a "service account". In this mode, you configure a separate account where
|
||||||
|
the service stores project data that it manages. This account is not used
|
||||||
|
directly by the end-user. Instead, all access is done through the service.
|
||||||
|
|
||||||
|
To access the "service" account, the service must present two tokens: one from
|
||||||
|
the end-user and another from its own service user. Only when both tokens are
|
||||||
|
present can the account be accessed. This section describes how to set the
|
||||||
|
configuration options to correctly control access to both the "normal" and
|
||||||
|
"service" accounts.
|
||||||
|
|
||||||
|
In this example, end users use the ``AUTH_`` prefix in account names,
|
||||||
|
whereas services use the ``SERVICE_`` prefix::
|
||||||
|
|
||||||
|
[filter:keystoneauth]
|
||||||
|
use = egg:swift#keystoneauth
|
||||||
|
reseller_prefix = AUTH, SERVICE
|
||||||
|
operator_roles = admin, swiftoperator
|
||||||
|
SERVICE_service_roles = service
|
||||||
|
|
||||||
|
The actual values for these variable will need to be set depending on your
|
||||||
|
situation as follows:
|
||||||
|
|
||||||
|
* The first item in the reseller_prefix list must match Keystone's endpoint
|
||||||
|
(see ``/etc/keystone/default_catalog.templates`` above). Normally
|
||||||
|
this is ``AUTH``.
|
||||||
|
* The second item in the reseller_prefix list is the prefix used by the
|
||||||
|
Openstack services(s). You must configure this value (``SERVICE`` in the
|
||||||
|
example) with whatever the other Openstack service(s) use.
|
||||||
|
* Set the operator_roles option to contain a role or roles that end-user's
|
||||||
|
have on project's they use.
|
||||||
|
* Set the SERVICE_service_roles value to a role or roles that only the
|
||||||
|
Openstack service user has. Do not use a role that is assigned to
|
||||||
|
"normal" end users. In this example, the role ``service`` is used.
|
||||||
|
The service user is granted this role to a *single* project only. You do
|
||||||
|
not need to make the service user a member of every project.
|
||||||
|
|
||||||
|
This configuration works as follows:
|
||||||
|
|
||||||
|
* The end-user presents a user token to an Openstack service. The service
|
||||||
|
then makes a Swift request to the account with the ``SERVICE`` prefix.
|
||||||
|
* The service forwards the original user token with the request. It also
|
||||||
|
adds it's own service token.
|
||||||
|
* Swift validates both tokens. When validated, the user token gives the
|
||||||
|
``admin`` or ``swiftoperator`` role(s). When validated, the service token
|
||||||
|
gives the ``service`` role.
|
||||||
|
* Swift interprets the above configuration as follows:
|
||||||
|
* Did the user token provide one of the roles listed in operator_roles?
|
||||||
|
* Did the service token have the ``service`` role as described by the
|
||||||
|
``SERVICE_service_roles`` options.
|
||||||
|
* If both conditions are met, the request is granted. Otherwise, Swift
|
||||||
|
rejects the request.
|
||||||
|
|
||||||
|
In the above example, all services share the same account. You can separate
|
||||||
|
each service into its own account. For example, the following provides a
|
||||||
|
dedicated account for each of the Glance and Cinder services. In addition,
|
||||||
|
you must assign the ``glance_service`` and ``cinder_service`` to the
|
||||||
|
appropriate service users::
|
||||||
|
|
||||||
|
[filter:keystoneauth]
|
||||||
|
use = egg:swift#keystoneauth
|
||||||
|
reseller_prefix = AUTH, IMAGE, VOLUME
|
||||||
|
operator_roles = admin, swiftoperator
|
||||||
|
IMAGE_service_roles = glance_service
|
||||||
|
VOLUME_service_roles = cinder_service
|
||||||
|
|
||||||
|
|
||||||
Access control using keystoneauth
|
Access control using keystoneauth
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
272
doc/source/overview_backing_store.rst
Normal file
272
doc/source/overview_backing_store.rst
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
|
||||||
|
=============================================
|
||||||
|
Using Swift as Backing Store for Service Data
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
----------
|
||||||
|
Background
|
||||||
|
----------
|
||||||
|
|
||||||
|
This section provides guidance to OpenStack Service developers for how to
|
||||||
|
store your users' data in Swift. An example of this is that a user requests
|
||||||
|
that Nova save a snapshot of a VM. Nova passes the request to Glance,
|
||||||
|
Glance writes the image to a Swift container as a set of objects.
|
||||||
|
|
||||||
|
Throughout this section, the following terminology and concepts are used:
|
||||||
|
|
||||||
|
* User or end-user. This is a person making a request that will result in
|
||||||
|
an Openstack Service making a request to Swift.
|
||||||
|
|
||||||
|
* Project (also known as Tenant). This is the unit of resource ownership.
|
||||||
|
While data such as snapshot images or block volume backups may be
|
||||||
|
stored as a result of an end-user's request, the reality is that these
|
||||||
|
are project data.
|
||||||
|
|
||||||
|
* Service. This is a program or system used by end-users. Specifically, it
|
||||||
|
is any program or system that is capable of receiving end-user's tokens and
|
||||||
|
validating the token with the Keystone Service and has a need to store
|
||||||
|
data in Swift. Glance and Cinder are examples of such Services.
|
||||||
|
|
||||||
|
* Service User. This is a Keystone user that has been assigned to a Service.
|
||||||
|
This allows the Service to generate and use its own tokens so that it
|
||||||
|
can interact with other Services as itself.
|
||||||
|
|
||||||
|
* Service Project. This is a project (tenant) that is associated with a
|
||||||
|
Service. There may be a single project shared by many Services or there
|
||||||
|
may be a project dedicated to each Service. In this document, the
|
||||||
|
main purpose of the Service Project is to allow the system operator
|
||||||
|
to configure specific roles for each Service User.
|
||||||
|
|
||||||
|
-------------------------------
|
||||||
|
Alternate Backing Store Schemes
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
There are three schemes described here:
|
||||||
|
|
||||||
|
* Dedicated Service Account (Single Tenant)
|
||||||
|
|
||||||
|
Your Service has a dedicated Service Project (hence a single dedicated
|
||||||
|
Swift account). Data for all users and projects are stored in this
|
||||||
|
account. Your Service must have a user assigned to it (the Service User).
|
||||||
|
When you have data to store on behalf of one of your users, you use the
|
||||||
|
Service User credentials to get a token for the Service Project and
|
||||||
|
request Swift to store the data in the Service Project.
|
||||||
|
|
||||||
|
With this scheme, data for all users is stored in a single account. This
|
||||||
|
is transparent to your users and since the credentials for the Service User
|
||||||
|
are typically not shared with anyone, your users' cannot access their
|
||||||
|
data by making a request directly to Swift. However, since data belonging
|
||||||
|
to all users is stored in one account, it presents a single point of
|
||||||
|
vulnerably to accidental deletion or a leak of the service-user
|
||||||
|
credentials.
|
||||||
|
|
||||||
|
* Multi Project (Multi Tenant)
|
||||||
|
|
||||||
|
Data belonging to a project is stored in the Swift account
|
||||||
|
associated with the project. Users make requests to your Service using
|
||||||
|
a token scoped to a project in the normal way. You can then use this
|
||||||
|
same token to store the user data in the project's Swift account.
|
||||||
|
|
||||||
|
The effect is that data is stored in multiple projects (aka tenants).
|
||||||
|
Hence this scheme has been known as the "multi tenant" scheme.
|
||||||
|
|
||||||
|
With this scheme, access is controlled by Keystone. The users must
|
||||||
|
have a role that allows them to perform the request to your Service. In
|
||||||
|
addition, they must have a role that also allows them to store data in
|
||||||
|
the Swift account. By default, the admin or swiftoperator roles are
|
||||||
|
used for this purpose (specific systems may use other role names). If the
|
||||||
|
user does not have the appropriate roles, when your Service attempts
|
||||||
|
to access Swift, the operation will fail.
|
||||||
|
|
||||||
|
Since you are using the user's token to access the data, it follows that
|
||||||
|
the user can use the same token to access Swift directly -- bypassing your
|
||||||
|
Service. When end-users are browsing containers, they will also see
|
||||||
|
your Service's containers and objects -- and may potentially delete
|
||||||
|
the data. Conversely, there is no single account where all data so leakage
|
||||||
|
of credentials will only affect a single project/tenant.
|
||||||
|
|
||||||
|
* Service Prefix Account
|
||||||
|
|
||||||
|
Data belonging to a project is stored in a Swift account associated
|
||||||
|
with the project. This is similar to the Multi Project scheme described
|
||||||
|
above. However, the Swift account is different than the account that
|
||||||
|
users access. Specifically, it has a different account prefix. For example,
|
||||||
|
for the project 1234, the user account is named AUTH_1234. Your Service uses
|
||||||
|
a different account, for example, SERVICE_1234.
|
||||||
|
|
||||||
|
To access the SERVICE_1234 account, you must present two tokens: the user's
|
||||||
|
token is put in the X-Auth-Token header. You present your Service's token
|
||||||
|
in the X-Service-Token header. Swift is configured such that only when both
|
||||||
|
tokens are presented will it allow access. Specifically, the user cannot
|
||||||
|
bypass your Service because they only have their own token. Conversely, your
|
||||||
|
Service can only access the data while it has a copy of the user's token --
|
||||||
|
the Service's token by itself will not grant access.
|
||||||
|
|
||||||
|
The data stored in the Service Prefix Account cannot be seen by end-users.
|
||||||
|
So they cannot delete this data -- they can only access the data if they
|
||||||
|
make a request through your Service. The data is also more secure. To make
|
||||||
|
an unauthorized access, someone would need to compromise both an end-user's
|
||||||
|
and your Service User credentials. Even then, this would only expose one
|
||||||
|
project -- not other projects.
|
||||||
|
|
||||||
|
The Service Prefix Account scheme combines features of the Dedicated Service
|
||||||
|
Account and Multi Project schemes. It has the private, dedicated,
|
||||||
|
characteristics of the Dedicated Service Account scheme but does not present
|
||||||
|
a single point of attack. Using the Service Prefix Account scheme is a little
|
||||||
|
more involved than the other schemes, so the rest of this document describes
|
||||||
|
it more detail.
|
||||||
|
|
||||||
|
-------------------------------
|
||||||
|
Service Prefix Account Overview
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
The following diagram shows the flow through the system from the end-user,
|
||||||
|
to your Service and then onto Swift::
|
||||||
|
|
||||||
|
client
|
||||||
|
\
|
||||||
|
\ <request>: <path-specific-to-the-service>
|
||||||
|
\ x-auth-token: <user-token>
|
||||||
|
\
|
||||||
|
SERVICE
|
||||||
|
\
|
||||||
|
\ PUT: /v1/SERVICE_1234/<container>/<object>
|
||||||
|
\ x-auth-token: <user-token>
|
||||||
|
\ x-service-token: <service-token>
|
||||||
|
\
|
||||||
|
Swift
|
||||||
|
|
||||||
|
The sequence of events and actions are as follows:
|
||||||
|
|
||||||
|
* Request arrives at your Service
|
||||||
|
|
||||||
|
* The <user-token> is validated by the keystonemiddleware.auth_token
|
||||||
|
middleware. The user's role(s) are used to determine if the user
|
||||||
|
can perform the request. See :doc:`overview_auth` for technical
|
||||||
|
information on the authentication system.
|
||||||
|
|
||||||
|
* As part of this request, your Service needs to access Swift (either to
|
||||||
|
write or read a container or object). In this example, you want to perform
|
||||||
|
a PUT on <container>/<object>.
|
||||||
|
|
||||||
|
* In the wsgi environment, the auth_token module will have populated the
|
||||||
|
HTTP_X_SERVICE_CATALOG item. This lists the Swift endpoint and account.
|
||||||
|
This is something such as https://<netloc>/v1/AUTH_1234 where ``AUTH_``
|
||||||
|
is a prefix and ``1234`` is the project id.
|
||||||
|
|
||||||
|
* The ``AUTH_`` prefix is the default value. However, your system may use a
|
||||||
|
different prefix. To determine the actual prefix, search for the first
|
||||||
|
underscore ('_') character in the account name. If there is no underscore
|
||||||
|
character in the account name, this means there is no prefix.
|
||||||
|
|
||||||
|
* Your Service should have a configuration parameter that provides the
|
||||||
|
appropriate prefix to use for storing data in Swift. There is more
|
||||||
|
discussion of this below, but for now assume the prefix is ``SERVICE_``.
|
||||||
|
|
||||||
|
* Replace the prefix (``AUTH_`` in above examples) in the path with
|
||||||
|
``SERVICE_``, so the full URL to access the object becomes
|
||||||
|
https://<netloc>/v1/SERVICE_1234/<container>/<object>.
|
||||||
|
|
||||||
|
* Make the request to Swift, using this URL. In the X-Auth-Token header place
|
||||||
|
a copy of the <user-token>. In the X-Service-Token header, place your
|
||||||
|
Service's token. If you use python-swiftclient you can achieve this
|
||||||
|
by:
|
||||||
|
* Putting the URL in the ``preauthurl`` parameter
|
||||||
|
* Putting the <user-token> in ``preauthtoken`` paramater
|
||||||
|
* Adding the X-Service-Token to the ``headers`` parameter
|
||||||
|
|
||||||
|
|
||||||
|
Using the HTTP_X_SERVICE_CATALOG to get Swift Account Name
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
The auth_token middleware populates the wsgi environment with information when
|
||||||
|
it validates the user's token. The HTTP_X_SERVICE_CATALOG item is a JSON
|
||||||
|
string containing details of the Openstack endpoints. For Swift, this also
|
||||||
|
contains the project's Swift account name. Here is an example of a catalog
|
||||||
|
entry for Swift::
|
||||||
|
|
||||||
|
"serviceCatalog": [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
....
|
||||||
|
"type": "object-store",
|
||||||
|
"endpoints": [
|
||||||
|
...
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"publicURL": "https://<netloc>/v1/AUTH_1234",
|
||||||
|
"region": "<region-name>"
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
To get the End-user's account:
|
||||||
|
|
||||||
|
* Look for an entry with ``type`` of ``object-store``
|
||||||
|
|
||||||
|
* If there are several regions, there will be several endpoints. Use the
|
||||||
|
appropriate region name and select the ``publicURL`` item.
|
||||||
|
|
||||||
|
* The Swift account name is the final item in the path ("AUTH_1234" in this
|
||||||
|
example).
|
||||||
|
|
||||||
|
Getting a Service Token
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
A Service Token is no different than any other token and is requested
|
||||||
|
from Keystone using user credentials and project in the usual way. The core
|
||||||
|
requirement is that your Service User has the appropriate role. In practice:
|
||||||
|
|
||||||
|
* Your Service must have a user assigned to it (the Service User).
|
||||||
|
|
||||||
|
* Your Service has a project assigned to it (the Service Project).
|
||||||
|
|
||||||
|
* The Service User must have a role on the Service Project. This role is
|
||||||
|
distinct from any of the normal end-user roles.
|
||||||
|
|
||||||
|
* The role used must the role configured in the /etc/swift/proxy-server.conf.
|
||||||
|
This is the ``<prefix>_service_roles`` option. In this example, the role
|
||||||
|
is the ``service`` role::
|
||||||
|
|
||||||
|
[keystoneauth]
|
||||||
|
reseller_prefix = AUTH_, SERVICE_
|
||||||
|
SERVICE_service_role = service
|
||||||
|
|
||||||
|
The ``service`` role should only be granted to Openstack Services. It should
|
||||||
|
not be granted to users.
|
||||||
|
|
||||||
|
Single or multiple Service Prefixes?
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Most of the examples used in this document used a single prefix. The
|
||||||
|
prefix, ``SERVICE`` was used. By using a single prefix, an operator is
|
||||||
|
allowing all Openstack Services to share the same account for data
|
||||||
|
associated with a given project. For test systems or deployments well protected
|
||||||
|
on private firewalled networks, this is appropriate.
|
||||||
|
|
||||||
|
However, if one Service is compromised, that Service can access
|
||||||
|
data created by another Service. To prevent this, multiple Service Prefixes may
|
||||||
|
be used. This also requires that the operator configure multiple service
|
||||||
|
roles. For example, in a system that has Glance and Cinder, the following
|
||||||
|
Swift configuration could be used:
|
||||||
|
|
||||||
|
[keystoneauth]
|
||||||
|
reseller_prefix = AUTH_, IMAGE_, BLOCK_
|
||||||
|
IMAGE_service_roles = image_service
|
||||||
|
BLOCK_service_roles = block_service
|
||||||
|
|
||||||
|
The Service User for Glance would be granted the ``image_service`` role on its
|
||||||
|
Service Project and the Cinder Service user is granted the ``block_service``
|
||||||
|
role on its project. In this scheme, if the Cinder Service was compromised,
|
||||||
|
it would not be able to access any Glance data.
|
||||||
|
|
||||||
|
Container Naming
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Since a single Service Prefix is possible, container names should be prefixed
|
||||||
|
with a unique string to prevent name clashes. We suggest you use the service
|
||||||
|
type field (as used in the service catalog). For example, The Glance Service
|
||||||
|
would use "image" as a prefix.
|
@ -218,8 +218,22 @@ use = egg:swift#tempauth
|
|||||||
# attempting to validate it. Also, with authorization, only Swift storage
|
# attempting to validate it. Also, with authorization, only Swift storage
|
||||||
# accounts with this prefix will be authorized by this middleware. Useful if
|
# accounts with this prefix will be authorized by this middleware. Useful if
|
||||||
# multiple auth systems are in use for one Swift cluster.
|
# multiple auth systems are in use for one Swift cluster.
|
||||||
|
# The reseller_prefix may contain a comma separated list of items. The first
|
||||||
|
# item is used for the token as mentioned above. If second and subsequent
|
||||||
|
# items exist, the middleware will handle authorization for an account with
|
||||||
|
# that prefix. For example, for prefixes "AUTH, SERVICE", a path of
|
||||||
|
# /v1/SERVICE_account is handled the same as /v1/AUTH_account. If an empty
|
||||||
|
# (blank) reseller prefix is required, it must be first in the list. Two
|
||||||
|
# single quote characters indicates an empty (blank) reseller prefix.
|
||||||
# reseller_prefix = AUTH
|
# reseller_prefix = AUTH
|
||||||
|
|
||||||
#
|
#
|
||||||
|
# The require_group parameter names a group that must be presented by
|
||||||
|
# either X-Auth-Token or X-Service-Token. Usually this parameter is
|
||||||
|
# used only with multiple reseller prefixes (e.g., SERVICE_require_group=blah).
|
||||||
|
# By default, no group is needed. Do not use .admin.
|
||||||
|
# require_group =
|
||||||
|
|
||||||
# The auth prefix will cause requests beginning with this prefix to be routed
|
# The auth prefix will cause requests beginning with this prefix to be routed
|
||||||
# to the auth subsystem, for granting tokens, etc.
|
# to the auth subsystem, for granting tokens, etc.
|
||||||
# auth_prefix = /auth/
|
# auth_prefix = /auth/
|
||||||
@ -255,6 +269,7 @@ user_admin_admin = admin .admin .reseller_admin
|
|||||||
user_test_tester = testing .admin
|
user_test_tester = testing .admin
|
||||||
user_test2_tester2 = testing2 .admin
|
user_test2_tester2 = testing2 .admin
|
||||||
user_test_tester3 = testing3
|
user_test_tester3 = testing3
|
||||||
|
user_test5_tester5 = testing5 service
|
||||||
|
|
||||||
# To enable Keystone authentication you need to have the auth token
|
# To enable Keystone authentication you need to have the auth token
|
||||||
# middleware first to be configured. Here is an example below, please
|
# middleware first to be configured. Here is an example below, please
|
||||||
@ -283,8 +298,27 @@ user_test_tester3 = testing3
|
|||||||
#
|
#
|
||||||
# [filter:keystoneauth]
|
# [filter:keystoneauth]
|
||||||
# use = egg:swift#keystoneauth
|
# use = egg:swift#keystoneauth
|
||||||
# Operator roles is the role which user would be allowed to manage a
|
# The reseller_prefix option lists account namespaces that this middleware is
|
||||||
# tenant and be able to create container or give ACL to others.
|
# responsible for. The prefix is placed before the Keystone project id.
|
||||||
|
# For example, for project 12345678, and prefix AUTH, the account is
|
||||||
|
# named AUTH_12345678 (i.e., path is /v1/AUTH_12345678/...).
|
||||||
|
# Several prefixes are allowed by specifying a comma-separated list
|
||||||
|
# as in: "reseller_prefix = AUTH, SERVICE". The empty string indicates a
|
||||||
|
# single blank/empty prefix. If an empty prefix is required in a list of
|
||||||
|
# prefixes, a value of '' (two single quote characters) indicates a
|
||||||
|
# blank/empty prefix. Except for the blank/empty prefix, an underscore ('_')
|
||||||
|
# character is appended to the value unless already present.
|
||||||
|
# reseller_prefix = AUTH
|
||||||
|
#
|
||||||
|
# The user must have at least one role named by operator_roles on a
|
||||||
|
# project in order to create, delete and modify containers and objects
|
||||||
|
# and to set and read privileged headers such as ACLs.
|
||||||
|
# If there are several reseller prefix items, you can prefix the
|
||||||
|
# parameter so it applies only to those accounts (for example
|
||||||
|
# the parameter SERVICE_operator_roles applies to the /v1/SERVICE_<project>
|
||||||
|
# path). If you omit the prefix, the option applies to all reseller
|
||||||
|
# prefix items. For the blank/empty prefix, prefix with '' (do not put
|
||||||
|
# underscore after the two single quote characters).
|
||||||
# operator_roles = admin, swiftoperator
|
# operator_roles = admin, swiftoperator
|
||||||
#
|
#
|
||||||
# The reseller admin role has the ability to create and delete accounts
|
# The reseller admin role has the ability to create and delete accounts
|
||||||
@ -302,12 +336,25 @@ user_test_tester3 = testing3
|
|||||||
# compares names rather than UUIDs. This option is deprecated.
|
# compares names rather than UUIDs. This option is deprecated.
|
||||||
# is_admin = false
|
# is_admin = false
|
||||||
#
|
#
|
||||||
|
# If the service_roles parameter is present, an X-Service-Token must be
|
||||||
|
# present in the request that when validated, grants at least one role listed
|
||||||
|
# in the parameter. The X-Service-Token may be scoped to any project.
|
||||||
|
# If there are several reseller prefix items, you can prefix the
|
||||||
|
# parameter so it applies only to those accounts (for example
|
||||||
|
# the parameter SERVICE_service_roles applies to the /v1/SERVICE_<project>
|
||||||
|
# path). If you omit the prefix, the option applies to all reseller
|
||||||
|
# prefix items. For the blank/empty prefix, prefix with '' (do not put
|
||||||
|
# underscore after the two single quote characters).
|
||||||
|
# By default, no service_roles are required.
|
||||||
|
# service_roles =
|
||||||
|
#
|
||||||
# For backwards compatibility, keystoneauth will match names in cross-tenant
|
# For backwards compatibility, keystoneauth will match names in cross-tenant
|
||||||
# access control lists (ACLs) when both the requesting user and the tenant
|
# access control lists (ACLs) when both the requesting user and the tenant
|
||||||
# are in the default domain i.e the domain to which existing tenants are
|
# are in the default domain i.e the domain to which existing tenants are
|
||||||
# migrated. The default_domain_id value configured here should be the same as
|
# migrated. The default_domain_id value configured here should be the same as
|
||||||
# the value used during migration of tenants to keystone domains.
|
# the value used during migration of tenants to keystone domains.
|
||||||
# default_domain_id = default
|
# default_domain_id = default
|
||||||
|
#
|
||||||
# For a new installation, or an installation in which keystone projects may
|
# For a new installation, or an installation in which keystone projects may
|
||||||
# move between domains, you should disable backwards compatible name matching
|
# move between domains, you should disable backwards compatible name matching
|
||||||
# in ACLs by setting allow_names_in_acls to false:
|
# in ACLs by setting allow_names_in_acls to false:
|
||||||
|
@ -17,7 +17,7 @@ from swift.common.http import is_success
|
|||||||
from swift.common.middleware import acl as swift_acl
|
from swift.common.middleware import acl as swift_acl
|
||||||
from swift.common.request_helpers import get_sys_meta_prefix
|
from swift.common.request_helpers import get_sys_meta_prefix
|
||||||
from swift.common.swob import HTTPNotFound, HTTPForbidden, HTTPUnauthorized
|
from swift.common.swob import HTTPNotFound, HTTPForbidden, HTTPUnauthorized
|
||||||
from swift.common.utils import register_swift_info
|
from swift.common.utils import config_read_reseller_options, list_from_csv
|
||||||
from swift.proxy.controllers.base import get_account_info
|
from swift.proxy.controllers.base import get_account_info
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
@ -65,13 +65,16 @@ class KeystoneAuth(object):
|
|||||||
use = egg:swift#keystoneauth
|
use = egg:swift#keystoneauth
|
||||||
operator_roles = admin, swiftoperator
|
operator_roles = admin, swiftoperator
|
||||||
|
|
||||||
This maps tenants to account in Swift.
|
The user who is able to give ACL / create Containers permissions
|
||||||
|
will be the user with a role listed in the ``operator_roles``
|
||||||
The user whose able to give ACL / create Containers permissions
|
|
||||||
will be the one that are inside the ``operator_roles``
|
|
||||||
setting which by default includes the admin and the swiftoperator
|
setting which by default includes the admin and the swiftoperator
|
||||||
roles.
|
roles.
|
||||||
|
|
||||||
|
The keystoneauth middleware maps a Keystone project/tenant to an account
|
||||||
|
in Swift by adding a prefix (``AUTH_`` by default) to the tenant/project
|
||||||
|
id.. For example, if the project id is ``1234``, the path is
|
||||||
|
``/v1/AUTH_1234``.
|
||||||
|
|
||||||
If the ``is_admin`` option is ``true``, a user whose username is the same
|
If the ``is_admin`` option is ``true``, a user whose username is the same
|
||||||
as the project name and who has any role on the project will have access
|
as the project name and who has any role on the project will have access
|
||||||
rights elevated to be the same as if the user had one of the
|
rights elevated to be the same as if the user had one of the
|
||||||
@ -84,6 +87,48 @@ class KeystoneAuth(object):
|
|||||||
|
|
||||||
reseller_prefix = NEWAUTH
|
reseller_prefix = NEWAUTH
|
||||||
|
|
||||||
|
Don't forget to also update the Keystone service endpoint configuration to
|
||||||
|
use NEWAUTH in the path.
|
||||||
|
|
||||||
|
It is possible to have several accounts associated with the same project.
|
||||||
|
This is done by listing several prefixes as shown in the following
|
||||||
|
example:
|
||||||
|
|
||||||
|
reseller_prefix = AUTH, SERVICE
|
||||||
|
|
||||||
|
This means that for project id '1234', the paths '/v1/AUTH_1234' and
|
||||||
|
'/v1/SERVICE_1234' are associated with the project and are authorized
|
||||||
|
using roles that a user has with that project. The core use of this feature
|
||||||
|
is that it is possible to provide different rules for each account
|
||||||
|
prefix. The following parameters may be prefixed with the appropriate
|
||||||
|
prefix:
|
||||||
|
|
||||||
|
operator_roles
|
||||||
|
service_roles
|
||||||
|
|
||||||
|
For backward compatibility, no prefix implies the parameter
|
||||||
|
applies to all reseller_prefixes. Here is an example, using two
|
||||||
|
prefixes::
|
||||||
|
|
||||||
|
reseller_prefix = AUTH, SERVICE
|
||||||
|
# The next three lines have identical effects (since the first applies
|
||||||
|
# to both prefixes).
|
||||||
|
operator_roles = admin, swiftoperator
|
||||||
|
AUTH_operator_roles = admin, swiftoperator
|
||||||
|
SERVICE_operator_roles = admin, swiftoperator
|
||||||
|
# The next line only applies to accounts with the SERVICE prefix
|
||||||
|
SERVICE_operator_roles = admin, some_other_role
|
||||||
|
|
||||||
|
X-Service-Token tokens are supported by the inclusion of the service_roles
|
||||||
|
configuration option. When present, this option requires that the
|
||||||
|
X-Service-Token header supply a token from a user who has a role listed
|
||||||
|
in service_roles. Here is an example configuration::
|
||||||
|
|
||||||
|
reseller_prefix = AUTH, SERVICE
|
||||||
|
AUTH_operator_roles = admin, swiftoperator
|
||||||
|
SERVICE_operator_roles = admin, swiftoperator
|
||||||
|
SERVICE_service_roles = service
|
||||||
|
|
||||||
The keystoneauth middleware supports cross-tenant access control using
|
The keystoneauth middleware supports cross-tenant access control using
|
||||||
the syntax ``<tenant>:<user>`` to specify a grantee in container Access
|
the syntax ``<tenant>:<user>`` to specify a grantee in container Access
|
||||||
Control Lists (ACLs). For a request to be granted by an ACL, the grantee
|
Control Lists (ACLs). For a request to be granted by an ACL, the grantee
|
||||||
@ -135,11 +180,11 @@ class KeystoneAuth(object):
|
|||||||
self.app = app
|
self.app = app
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.logger = swift_utils.get_logger(conf, log_route='keystoneauth')
|
self.logger = swift_utils.get_logger(conf, log_route='keystoneauth')
|
||||||
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH_').strip()
|
self.reseller_prefixes, self.account_rules = \
|
||||||
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
|
config_read_reseller_options(conf,
|
||||||
self.reseller_prefix += '_'
|
dict(operator_roles=['admin',
|
||||||
self.operator_roles = conf.get('operator_roles',
|
'swiftoperator'],
|
||||||
'admin, swiftoperator').lower()
|
service_roles=[]))
|
||||||
self.reseller_admin_role = conf.get('reseller_admin_role',
|
self.reseller_admin_role = conf.get('reseller_admin_role',
|
||||||
'ResellerAdmin').lower()
|
'ResellerAdmin').lower()
|
||||||
config_is_admin = conf.get('is_admin', "false").lower()
|
config_is_admin = conf.get('is_admin', "false").lower()
|
||||||
@ -158,7 +203,7 @@ class KeystoneAuth(object):
|
|||||||
# authentication
|
# authentication
|
||||||
if (self.allow_overrides and
|
if (self.allow_overrides and
|
||||||
environ.get('swift.authorize_override', False)):
|
environ.get('swift.authorize_override', False)):
|
||||||
msg = 'Authorizing from an overriding middleware (i.e: tempurl)'
|
msg = 'Authorizing from an overriding middleware'
|
||||||
self.logger.debug(msg)
|
self.logger.debug(msg)
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
@ -212,14 +257,14 @@ class KeystoneAuth(object):
|
|||||||
"""Extract the identity from the Keystone auth component."""
|
"""Extract the identity from the Keystone auth component."""
|
||||||
if environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed':
|
if environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed':
|
||||||
return
|
return
|
||||||
roles = []
|
roles = list_from_csv(environ.get('HTTP_X_ROLES', ''))
|
||||||
if 'HTTP_X_ROLES' in environ:
|
service_roles = list_from_csv(environ.get('HTTP_X_SERVICE_ROLES', ''))
|
||||||
roles = environ['HTTP_X_ROLES'].split(',')
|
|
||||||
identity = {'user': (environ.get('HTTP_X_USER_ID'),
|
identity = {'user': (environ.get('HTTP_X_USER_ID'),
|
||||||
environ.get('HTTP_X_USER_NAME')),
|
environ.get('HTTP_X_USER_NAME')),
|
||||||
'tenant': (environ.get('HTTP_X_TENANT_ID'),
|
'tenant': (environ.get('HTTP_X_TENANT_ID'),
|
||||||
environ.get('HTTP_X_TENANT_NAME')),
|
environ.get('HTTP_X_TENANT_NAME')),
|
||||||
'roles': roles}
|
'roles': roles,
|
||||||
|
'service_roles': service_roles}
|
||||||
token_info = environ.get('keystone.token_info', {})
|
token_info = environ.get('keystone.token_info', {})
|
||||||
auth_version = 0
|
auth_version = 0
|
||||||
user_domain = project_domain = (None, None)
|
user_domain = project_domain = (None, None)
|
||||||
@ -237,12 +282,25 @@ class KeystoneAuth(object):
|
|||||||
identity['auth_version'] = auth_version
|
identity['auth_version'] = auth_version
|
||||||
return identity
|
return identity
|
||||||
|
|
||||||
def _get_account_for_tenant(self, tenant_id):
|
def _get_account_name(self, prefix, tenant_id):
|
||||||
return '%s%s' % (self.reseller_prefix, tenant_id)
|
return '%s%s' % (prefix, tenant_id)
|
||||||
|
|
||||||
def _reseller_check(self, account, tenant_id):
|
def _account_matches_tenant(self, account, tenant_id):
|
||||||
"""Check reseller prefix."""
|
"""Check if account belongs to a project/tenant"""
|
||||||
return account == self._get_account_for_tenant(tenant_id)
|
for prefix in self.reseller_prefixes:
|
||||||
|
if self._get_account_name(prefix, tenant_id) == account:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_account_prefix(self, account):
|
||||||
|
"""Get the prefix of an account"""
|
||||||
|
# Empty prefix matches everything, so try to match others first
|
||||||
|
for prefix in [pre for pre in self.reseller_prefixes if pre != '']:
|
||||||
|
if account.startswith(prefix):
|
||||||
|
return prefix
|
||||||
|
if '' in self.reseller_prefixes:
|
||||||
|
return ''
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_project_domain_id(self, environ):
|
def _get_project_domain_id(self, environ):
|
||||||
info = get_account_info(environ, self.app, 'KS')
|
info = get_account_info(environ, self.app, 'KS')
|
||||||
@ -269,7 +327,7 @@ class KeystoneAuth(object):
|
|||||||
tenant_id, tenant_name = env_identity['tenant']
|
tenant_id, tenant_name = env_identity['tenant']
|
||||||
exists, sysmeta_id = self._get_project_domain_id(req.environ)
|
exists, sysmeta_id = self._get_project_domain_id(req.environ)
|
||||||
req_has_id, req_id, new_id = False, None, None
|
req_has_id, req_id, new_id = False, None, None
|
||||||
if self._reseller_check(account, tenant_id):
|
if self._account_matches_tenant(account, tenant_id):
|
||||||
# domain id can be inferred from request (may be None)
|
# domain id can be inferred from request (may be None)
|
||||||
req_has_id = True
|
req_has_id = True
|
||||||
req_id = env_identity['project_domain'][0]
|
req_id = env_identity['project_domain'][0]
|
||||||
@ -304,7 +362,7 @@ class KeystoneAuth(object):
|
|||||||
# request user and scoped project are both in default domain
|
# request user and scoped project are both in default domain
|
||||||
tenant_id, tenant_name = identity['tenant']
|
tenant_id, tenant_name = identity['tenant']
|
||||||
version, account, container, obj = path_parts
|
version, account, container, obj = path_parts
|
||||||
if self._reseller_check(account, tenant_id):
|
if self._account_matches_tenant(account, tenant_id):
|
||||||
# account == scoped project, so account is also in default domain
|
# account == scoped project, so account is also in default domain
|
||||||
allow = True
|
allow = True
|
||||||
else:
|
else:
|
||||||
@ -365,6 +423,8 @@ class KeystoneAuth(object):
|
|||||||
self._set_project_domain_id(req, part, env_identity)
|
self._set_project_domain_id(req, part, env_identity)
|
||||||
|
|
||||||
user_roles = [r.lower() for r in env_identity.get('roles', [])]
|
user_roles = [r.lower() for r in env_identity.get('roles', [])]
|
||||||
|
user_service_roles = [r.lower() for r in env_identity.get(
|
||||||
|
'service_roles', [])]
|
||||||
|
|
||||||
# Give unconditional access to a user with the reseller_admin
|
# Give unconditional access to a user with the reseller_admin
|
||||||
# role.
|
# role.
|
||||||
@ -402,22 +462,36 @@ class KeystoneAuth(object):
|
|||||||
|
|
||||||
# Check if a user tries to access an account that does not match their
|
# Check if a user tries to access an account that does not match their
|
||||||
# token
|
# token
|
||||||
if not self._reseller_check(account, tenant_id):
|
if not self._account_matches_tenant(account, tenant_id):
|
||||||
log_msg = 'tenant mismatch: %s != %s'
|
log_msg = 'tenant mismatch: %s != %s'
|
||||||
self.logger.debug(log_msg, account, tenant_id)
|
self.logger.debug(log_msg, account, tenant_id)
|
||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
|
|
||||||
# Check the roles the user is belonging to. If the user is
|
# Compare roles from tokens against the configuration options:
|
||||||
# part of the role defined in the config variable
|
#
|
||||||
# operator_roles (like admin) then it will be
|
# X-Auth-Token role Has specified X-Service-Token role Grant
|
||||||
# promoted as an admin of the account/tenant.
|
# in operator_roles? service_roles? in service_roles? swift_owner?
|
||||||
for role in self.operator_roles.split(','):
|
# ------------------ -------------- -------------------- ------------
|
||||||
role = role.strip()
|
# yes yes yes yes
|
||||||
if role in user_roles:
|
# yes no don't care yes
|
||||||
log_msg = 'allow user with role %s as account admin'
|
# no don't care don't care no
|
||||||
self.logger.debug(log_msg, role)
|
# ------------------ -------------- -------------------- ------------
|
||||||
req.environ['swift_owner'] = True
|
account_prefix = self._get_account_prefix(account)
|
||||||
return
|
operator_roles = self.account_rules[account_prefix]['operator_roles']
|
||||||
|
have_operator_role = set(operator_roles).intersection(
|
||||||
|
set(user_roles))
|
||||||
|
service_roles = self.account_rules[account_prefix]['service_roles']
|
||||||
|
have_service_role = set(service_roles).intersection(
|
||||||
|
set(user_service_roles))
|
||||||
|
if have_operator_role and (service_roles and have_service_role):
|
||||||
|
req.environ['swift_owner'] = True
|
||||||
|
elif have_operator_role and not service_roles:
|
||||||
|
req.environ['swift_owner'] = True
|
||||||
|
if req.environ.get('swift_owner'):
|
||||||
|
log_msg = 'allow user with role(s) %s as account admin'
|
||||||
|
self.logger.debug(log_msg, ','.join(have_operator_role.union(
|
||||||
|
have_service_role)))
|
||||||
|
return
|
||||||
|
|
||||||
# If user is of the same name of the tenant then make owner of it.
|
# If user is of the same name of the tenant then make owner of it.
|
||||||
if self.is_admin and user_name == tenant_name:
|
if self.is_admin and user_name == tenant_name:
|
||||||
@ -457,7 +531,8 @@ class KeystoneAuth(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
is_authoritative_authz = (account and
|
is_authoritative_authz = (account and
|
||||||
account.startswith(self.reseller_prefix))
|
(self._get_account_prefix(account) in
|
||||||
|
self.reseller_prefixes))
|
||||||
if not is_authoritative_authz:
|
if not is_authoritative_authz:
|
||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
|
|
||||||
@ -508,7 +583,6 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
register_swift_info('keystoneauth')
|
|
||||||
|
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
return KeystoneAuth(app, conf)
|
return KeystoneAuth(app, conf)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2011 OpenStack Foundation
|
# Copyright (c) 2011-2014 OpenStack Foundation
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -32,7 +32,8 @@ from swift.common.request_helpers import get_sys_meta_prefix
|
|||||||
from swift.common.middleware.acl import (
|
from swift.common.middleware.acl import (
|
||||||
clean_acl, parse_acl, referrer_allowed, acls_from_account_info)
|
clean_acl, parse_acl, referrer_allowed, acls_from_account_info)
|
||||||
from swift.common.utils import cache_from_env, get_logger, \
|
from swift.common.utils import cache_from_env, get_logger, \
|
||||||
split_path, config_true_value, register_swift_info
|
split_path, config_true_value
|
||||||
|
from swift.common.utils import config_read_reseller_options
|
||||||
from swift.proxy.controllers.base import get_account_info
|
from swift.proxy.controllers.base import get_account_info
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +67,53 @@ class TempAuth(object):
|
|||||||
|
|
||||||
See the proxy-server.conf-sample for more information.
|
See the proxy-server.conf-sample for more information.
|
||||||
|
|
||||||
|
Multiple Reseller Prefix Items:
|
||||||
|
|
||||||
|
The reseller prefix specifies which parts of the account namespace this
|
||||||
|
middleware is responsible for managing authentication and authorization.
|
||||||
|
By default, the prefix is AUTH so accounts and tokens are prefixed
|
||||||
|
by AUTH_. When a request's token and/or path start with AUTH_, this
|
||||||
|
middleware knows it is responsible.
|
||||||
|
|
||||||
|
We allow the reseller prefix to be a list. In tempauth, the first item
|
||||||
|
in the list is used as the prefix for tokens and user groups. The
|
||||||
|
other prefixes provide alternate accounts that user's can access. For
|
||||||
|
example if the reseller prefix list is 'AUTH, OTHER', a user with
|
||||||
|
admin access to AUTH_account also has admin access to
|
||||||
|
OTHER_account.
|
||||||
|
|
||||||
|
Required Group:
|
||||||
|
|
||||||
|
The group .admin is normally needed to access an account (ACLs provide
|
||||||
|
an additional way to access an account). You can specify the
|
||||||
|
``require_group`` parameter. This means that you also need the named group
|
||||||
|
to access an account. If you have several reseller prefix items, prefix
|
||||||
|
the ``require_group`` parameter with the appropriate prefix.
|
||||||
|
|
||||||
|
X-Service-Token:
|
||||||
|
|
||||||
|
If an X-Service-Token is presented in the request headers, the groups
|
||||||
|
derived from the token are appended to the roles derived form
|
||||||
|
X-Auth-Token. If X-Auth-Token is missing or invalid, X-Service-Token
|
||||||
|
is not processed.
|
||||||
|
|
||||||
|
The X-Service-Token is useful when combined with multiple reseller prefix
|
||||||
|
items. In the following configuration, accounts prefixed SERVICE_
|
||||||
|
are only accessible if X-Auth-Token is form the end-user and
|
||||||
|
X-Service-Token is from the ``glance`` user::
|
||||||
|
|
||||||
|
[filter:tempauth]
|
||||||
|
use = egg:swift#tempauth
|
||||||
|
reseller_prefix = AUTH, SERVICE
|
||||||
|
SERVICE_require_group = .service
|
||||||
|
user_admin_admin = admin .admin .reseller_admin
|
||||||
|
user_joeacct_joe = joepw .admin
|
||||||
|
user_maryacct_mary = marypw .admin
|
||||||
|
user_glance_glance = glancepw .service
|
||||||
|
|
||||||
|
The name .service is an example. Unlike .admin and .reseller_admin
|
||||||
|
it is not a reserved name.
|
||||||
|
|
||||||
Account ACLs:
|
Account ACLs:
|
||||||
If a swift_owner issues a POST or PUT to the account, with the
|
If a swift_owner issues a POST or PUT to the account, with the
|
||||||
X-Account-Access-Control header set in the request, then this may
|
X-Account-Access-Control header set in the request, then this may
|
||||||
@ -112,9 +160,9 @@ class TempAuth(object):
|
|||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.logger = get_logger(conf, log_route='tempauth')
|
self.logger = get_logger(conf, log_route='tempauth')
|
||||||
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
|
self.log_headers = config_true_value(conf.get('log_headers', 'f'))
|
||||||
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
|
self.reseller_prefixes, self.account_rules = \
|
||||||
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
|
config_read_reseller_options(conf, dict(require_group=''))
|
||||||
self.reseller_prefix += '_'
|
self.reseller_prefix = self.reseller_prefixes[0]
|
||||||
self.logger.set_statsd_prefix('tempauth.%s' % (
|
self.logger.set_statsd_prefix('tempauth.%s' % (
|
||||||
self.reseller_prefix if self.reseller_prefix else 'NONE',))
|
self.reseller_prefix if self.reseller_prefix else 'NONE',))
|
||||||
self.auth_prefix = conf.get('auth_prefix', '/auth/')
|
self.auth_prefix = conf.get('auth_prefix', '/auth/')
|
||||||
@ -179,9 +227,14 @@ class TempAuth(object):
|
|||||||
return self.handle(env, start_response)
|
return self.handle(env, start_response)
|
||||||
s3 = env.get('HTTP_AUTHORIZATION')
|
s3 = env.get('HTTP_AUTHORIZATION')
|
||||||
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
||||||
|
service_token = env.get('HTTP_X_SERVICE_TOKEN')
|
||||||
if s3 or (token and token.startswith(self.reseller_prefix)):
|
if s3 or (token and token.startswith(self.reseller_prefix)):
|
||||||
# Note: Empty reseller_prefix will match all tokens.
|
# Note: Empty reseller_prefix will match all tokens.
|
||||||
groups = self.get_groups(env, token)
|
groups = self.get_groups(env, token)
|
||||||
|
if service_token:
|
||||||
|
service_groups = self.get_groups(env, service_token)
|
||||||
|
if groups and service_groups:
|
||||||
|
groups += ',' + service_groups
|
||||||
if groups:
|
if groups:
|
||||||
user = groups and groups.split(',', 1)[0] or ''
|
user = groups and groups.split(',', 1)[0] or ''
|
||||||
trans_id = env.get('swift.trans_id')
|
trans_id = env.get('swift.trans_id')
|
||||||
@ -211,42 +264,102 @@ class TempAuth(object):
|
|||||||
elif 'swift.authorize' not in env:
|
elif 'swift.authorize' not in env:
|
||||||
env['swift.authorize'] = self.denied_response
|
env['swift.authorize'] = self.denied_response
|
||||||
else:
|
else:
|
||||||
if self.reseller_prefix:
|
if self._is_definitive_auth(env.get('PATH_INFO', '')):
|
||||||
# With a non-empty reseller_prefix, I would like to be called
|
# Handle anonymous access to accounts I'm the definitive
|
||||||
# back for anonymous access to accounts I know I'm the
|
# auth for.
|
||||||
# definitive auth for.
|
|
||||||
try:
|
|
||||||
version, rest = split_path(env.get('PATH_INFO', ''),
|
|
||||||
1, 2, True)
|
|
||||||
except ValueError:
|
|
||||||
version, rest = None, None
|
|
||||||
self.logger.increment('errors')
|
|
||||||
if rest and rest.startswith(self.reseller_prefix):
|
|
||||||
# Handle anonymous access to accounts I'm the definitive
|
|
||||||
# auth for.
|
|
||||||
env['swift.authorize'] = self.authorize
|
|
||||||
env['swift.clean_acl'] = clean_acl
|
|
||||||
# Not my token, not my account, I can't authorize this request,
|
|
||||||
# deny all is a good idea if not already set...
|
|
||||||
elif 'swift.authorize' not in env:
|
|
||||||
env['swift.authorize'] = self.denied_response
|
|
||||||
# Because I'm not certain if I'm the definitive auth for empty
|
|
||||||
# reseller_prefixed accounts, I won't overwrite swift.authorize.
|
|
||||||
elif 'swift.authorize' not in env:
|
|
||||||
env['swift.authorize'] = self.authorize
|
env['swift.authorize'] = self.authorize
|
||||||
env['swift.clean_acl'] = clean_acl
|
env['swift.clean_acl'] = clean_acl
|
||||||
|
elif self.reseller_prefix == '':
|
||||||
|
# Because I'm not certain if I'm the definitive auth, I won't
|
||||||
|
# overwrite swift.authorize.
|
||||||
|
if 'swift.authorize' not in env:
|
||||||
|
env['swift.authorize'] = self.authorize
|
||||||
|
env['swift.clean_acl'] = clean_acl
|
||||||
|
else:
|
||||||
|
# Not my token, not my account, I can't authorize this request,
|
||||||
|
# deny all is a good idea if not already set...
|
||||||
|
if 'swift.authorize' not in env:
|
||||||
|
env['swift.authorize'] = self.denied_response
|
||||||
|
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
|
|
||||||
|
def _is_definitive_auth(self, path):
|
||||||
|
"""
|
||||||
|
Determine if we are the definitive auth
|
||||||
|
|
||||||
|
Determines if we are the definitive auth for a given path.
|
||||||
|
If the account name is prefixed with something matching one
|
||||||
|
of the reseller_prefix items, then we are the auth (return True)
|
||||||
|
Non-matching: we are not the auth.
|
||||||
|
However, one of the reseller_prefix items can be blank. If
|
||||||
|
so, we cannot always be definite so return False.
|
||||||
|
|
||||||
|
:param path: A path (e.g., /v1/AUTH_joesaccount/c/o)
|
||||||
|
:return:True if we are definitive auth
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
version, account, rest = split_path(path, 1, 3, True)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
if account:
|
||||||
|
return bool(self._get_account_prefix(account))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _non_empty_reseller_prefixes(self):
|
||||||
|
return iter([pre for pre in self.reseller_prefixes if pre != ''])
|
||||||
|
|
||||||
|
def _get_account_prefix(self, account):
|
||||||
|
"""
|
||||||
|
Get the prefix of an account
|
||||||
|
|
||||||
|
Determines which reseller prefix matches the account and returns
|
||||||
|
that prefix. If account does not start with one of the known
|
||||||
|
reseller prefixes, returns None.
|
||||||
|
|
||||||
|
:param account: Account name (e.g., AUTH_joesaccount) or None
|
||||||
|
:return: The prefix string (examples: 'AUTH_', 'SERVICE_', '')
|
||||||
|
If we can't match the prefix of the account, return None
|
||||||
|
"""
|
||||||
|
if account is None:
|
||||||
|
return None
|
||||||
|
# Empty prefix matches everything, so try to match others first
|
||||||
|
for prefix in self._non_empty_reseller_prefixes():
|
||||||
|
if account.startswith(prefix):
|
||||||
|
return prefix
|
||||||
|
if '' in self.reseller_prefixes:
|
||||||
|
return ''
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _dot_account(self, account):
|
||||||
|
"""
|
||||||
|
Detect if account starts with dot character after the prefix
|
||||||
|
|
||||||
|
:param account: account in path (e.g., AUTH_joesaccount)
|
||||||
|
:return:True if name starts with dot character
|
||||||
|
"""
|
||||||
|
prefix = self._get_account_prefix(account)
|
||||||
|
return prefix is not None and account[len(prefix)] == '.'
|
||||||
|
|
||||||
def _get_user_groups(self, account, account_user, account_id):
|
def _get_user_groups(self, account, account_user, account_id):
|
||||||
"""
|
"""
|
||||||
:param account: example: test
|
:param account: example: test
|
||||||
:param account_user: example: test:tester
|
:param account_user: example: test:tester
|
||||||
|
:param account_id: example: AUTH_test
|
||||||
|
:return: a comma separated string of group names. The group names are
|
||||||
|
as follows: account,account_user,groups...
|
||||||
|
If .admin is in the groups, this is replaced by all the
|
||||||
|
possible account ids. For example, for user joe, account acct
|
||||||
|
and resellers AUTH_, OTHER_, the returned string is as
|
||||||
|
follows: acct,acct:joe,AUTH_acct,OTHER_acct
|
||||||
"""
|
"""
|
||||||
groups = [account, account_user]
|
groups = [account, account_user]
|
||||||
groups.extend(self.users[account_user]['groups'])
|
groups.extend(self.users[account_user]['groups'])
|
||||||
if '.admin' in groups:
|
if '.admin' in groups:
|
||||||
groups.remove('.admin')
|
groups.remove('.admin')
|
||||||
groups.append(account_id)
|
for prefix in self._non_empty_reseller_prefixes():
|
||||||
|
groups.append('%s%s' % (prefix, account))
|
||||||
|
if account_id not in groups:
|
||||||
|
groups.append(account_id)
|
||||||
groups = ','.join(groups)
|
groups = ','.join(groups)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
@ -256,7 +369,6 @@ class TempAuth(object):
|
|||||||
|
|
||||||
:param env: The current WSGI environment dictionary.
|
:param env: The current WSGI environment dictionary.
|
||||||
:param token: Token to validate and return a group string for.
|
:param token: Token to validate and return a group string for.
|
||||||
|
|
||||||
:returns: None if the token is invalid or a string containing a comma
|
:returns: None if the token is invalid or a string containing a comma
|
||||||
separated list of groups the authenticated user is a member
|
separated list of groups the authenticated user is a member
|
||||||
of. The first group in the list is also considered a unique
|
of. The first group in the list is also considered a unique
|
||||||
@ -287,7 +399,7 @@ class TempAuth(object):
|
|||||||
s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
|
s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
|
||||||
if s != sign:
|
if s != sign:
|
||||||
return None
|
return None
|
||||||
groups = self._get_user_groups(account, account_user, account_id)
|
groups = self._get_user_groups(account, account_user)
|
||||||
|
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
@ -356,17 +468,16 @@ class TempAuth(object):
|
|||||||
Returns None if the request is authorized to continue or a standard
|
Returns None if the request is authorized to continue or a standard
|
||||||
WSGI response callable if not.
|
WSGI response callable if not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_junk, account, container, obj = req.split_path(1, 4, True)
|
_junk, account, container, obj = req.split_path(1, 4, True)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.logger.increment('errors')
|
self.logger.increment('errors')
|
||||||
return HTTPNotFound(request=req)
|
return HTTPNotFound(request=req)
|
||||||
|
|
||||||
if not account or not account.startswith(self.reseller_prefix):
|
if self._get_account_prefix(account) is None:
|
||||||
self.logger.debug("Account name: %s doesn't start with "
|
self.logger.debug("Account name: %s doesn't start with "
|
||||||
"reseller_prefix: %s."
|
"reseller_prefix(s): %s."
|
||||||
% (account, self.reseller_prefix))
|
% (account, ','.join(self.reseller_prefixes)))
|
||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
|
|
||||||
# At this point, TempAuth is convinced that it is authoritative.
|
# At this point, TempAuth is convinced that it is authoritative.
|
||||||
@ -385,8 +496,8 @@ class TempAuth(object):
|
|||||||
account_user = user_groups[1] if len(user_groups) > 1 else None
|
account_user = user_groups[1] if len(user_groups) > 1 else None
|
||||||
|
|
||||||
if '.reseller_admin' in user_groups and \
|
if '.reseller_admin' in user_groups and \
|
||||||
account != self.reseller_prefix and \
|
account not in self.reseller_prefixes and \
|
||||||
account[len(self.reseller_prefix)] != '.':
|
not self._dot_account(account):
|
||||||
req.environ['swift_owner'] = True
|
req.environ['swift_owner'] = True
|
||||||
self.logger.debug("User %s has reseller admin authorizing."
|
self.logger.debug("User %s has reseller admin authorizing."
|
||||||
% account_user)
|
% account_user)
|
||||||
@ -394,12 +505,22 @@ class TempAuth(object):
|
|||||||
|
|
||||||
if account in user_groups and \
|
if account in user_groups and \
|
||||||
(req.method not in ('DELETE', 'PUT') or container):
|
(req.method not in ('DELETE', 'PUT') or container):
|
||||||
# If the user is admin for the account and is not trying to do an
|
# The user is admin for the account and is not trying to do an
|
||||||
# account DELETE or PUT...
|
# account DELETE or PUT
|
||||||
req.environ['swift_owner'] = True
|
account_prefix = self._get_account_prefix(account)
|
||||||
self.logger.debug("User %s has admin authorizing."
|
require_group = self.account_rules.get(account_prefix).get(
|
||||||
% account_user)
|
'require_group')
|
||||||
return None
|
if require_group and require_group in user_groups:
|
||||||
|
req.environ['swift_owner'] = True
|
||||||
|
self.logger.debug("User %s has admin and %s group."
|
||||||
|
" Authorizing." % (account_user,
|
||||||
|
require_group))
|
||||||
|
return None
|
||||||
|
elif not require_group:
|
||||||
|
req.environ['swift_owner'] = True
|
||||||
|
self.logger.debug("User %s has admin authorizing."
|
||||||
|
% account_user)
|
||||||
|
return None
|
||||||
|
|
||||||
if (req.environ.get('swift_sync_key')
|
if (req.environ.get('swift_sync_key')
|
||||||
and (req.environ['swift_sync_key'] ==
|
and (req.environ['swift_sync_key'] ==
|
||||||
@ -648,7 +769,6 @@ def filter_factory(global_conf, **local_conf):
|
|||||||
"""Returns a WSGI filter app for use with paste.deploy."""
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
register_swift_info('tempauth', account_acls=True)
|
|
||||||
|
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
return TempAuth(app, conf)
|
return TempAuth(app, conf)
|
||||||
|
@ -291,6 +291,72 @@ def config_auto_int_value(value, default):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def append_underscore(prefix):
|
||||||
|
if prefix and prefix[-1] != '_':
|
||||||
|
prefix += '_'
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
|
||||||
|
def config_read_reseller_options(conf, defaults):
|
||||||
|
"""
|
||||||
|
Read reseller_prefix option and associated options from configuration
|
||||||
|
|
||||||
|
Reads the reseller_prefix option, then reads options that may be
|
||||||
|
associated with a specific reseller prefix. Reads options such that an
|
||||||
|
option without a prefix applies to all reseller prefixes unless an option
|
||||||
|
has an explicit prefix.
|
||||||
|
|
||||||
|
:param conf: the configuration
|
||||||
|
:param defaults: a dict of default values. The key is the option
|
||||||
|
name. The value is either an array of strings or a string
|
||||||
|
:return: tuple of an array of reseller prefixes and a dict of option values
|
||||||
|
"""
|
||||||
|
reseller_prefix_opt = conf.get('reseller_prefix', 'AUTH').split(',')
|
||||||
|
reseller_prefixes = []
|
||||||
|
for prefix in [pre.strip() for pre in reseller_prefix_opt if pre.strip()]:
|
||||||
|
if prefix == "''":
|
||||||
|
prefix = ''
|
||||||
|
prefix = append_underscore(prefix)
|
||||||
|
if prefix not in reseller_prefixes:
|
||||||
|
reseller_prefixes.append(prefix)
|
||||||
|
if len(reseller_prefixes) == 0:
|
||||||
|
reseller_prefixes.append('')
|
||||||
|
|
||||||
|
# Get prefix-using config options
|
||||||
|
associated_options = {}
|
||||||
|
for prefix in reseller_prefixes:
|
||||||
|
associated_options[prefix] = dict(defaults)
|
||||||
|
associated_options[prefix].update(
|
||||||
|
config_read_prefixed_options(conf, '', defaults))
|
||||||
|
prefix_name = prefix if prefix != '' else "''"
|
||||||
|
associated_options[prefix].update(
|
||||||
|
config_read_prefixed_options(conf, prefix_name, defaults))
|
||||||
|
return reseller_prefixes, associated_options
|
||||||
|
|
||||||
|
|
||||||
|
def config_read_prefixed_options(conf, prefix_name, defaults):
|
||||||
|
"""
|
||||||
|
Read prefixed options from configuration
|
||||||
|
|
||||||
|
:param conf: the configuration
|
||||||
|
:param prefix_name: the prefix (including, if needed, an underscore)
|
||||||
|
:param defaults: a dict of default values. The dict supplies the
|
||||||
|
option name and type (string or comma separated string)
|
||||||
|
:return: a dict containing the options
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
for option_name in defaults.keys():
|
||||||
|
value = conf.get('%s%s' % (prefix_name, option_name))
|
||||||
|
if value:
|
||||||
|
if isinstance(defaults.get(option_name), list):
|
||||||
|
params[option_name] = []
|
||||||
|
for role in value.lower().split(','):
|
||||||
|
params[option_name].append(role.strip())
|
||||||
|
else:
|
||||||
|
params[option_name] = value.strip()
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
def noop_libc_function(*args):
|
def noop_libc_function(*args):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -82,15 +82,15 @@ normalized_urls = None
|
|||||||
# If no config was read, we will fall back to old school env vars
|
# If no config was read, we will fall back to old school env vars
|
||||||
swift_test_auth_version = None
|
swift_test_auth_version = None
|
||||||
swift_test_auth = os.environ.get('SWIFT_TEST_AUTH')
|
swift_test_auth = os.environ.get('SWIFT_TEST_AUTH')
|
||||||
swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None, '']
|
swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None, '', '']
|
||||||
swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None, '']
|
swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None, '', '']
|
||||||
swift_test_tenant = ['', '', '', '']
|
swift_test_tenant = ['', '', '', '', '']
|
||||||
swift_test_perm = ['', '', '', '']
|
swift_test_perm = ['', '', '', '', '']
|
||||||
swift_test_domain = ['', '', '', '']
|
swift_test_domain = ['', '', '', '', '']
|
||||||
swift_test_user_id = ['', '', '', '']
|
swift_test_user_id = ['', '', '', '', '']
|
||||||
swift_test_tenant_id = ['', '', '', '']
|
swift_test_tenant_id = ['', '', '', '', '']
|
||||||
|
|
||||||
skip, skip2, skip3 = False, False, False
|
skip, skip2, skip3, skip_service_tokens = False, False, False, False
|
||||||
|
|
||||||
orig_collate = ''
|
orig_collate = ''
|
||||||
insecure = False
|
insecure = False
|
||||||
@ -207,11 +207,14 @@ def in_process_setup(the_object_server=object_server):
|
|||||||
# User on same account as first, but without admin access
|
# User on same account as first, but without admin access
|
||||||
'username3': 'tester3',
|
'username3': 'tester3',
|
||||||
'password3': 'testing3',
|
'password3': 'testing3',
|
||||||
# For tempauth middleware
|
# Service user and prefix (emulates glance, cinder, etc. user)
|
||||||
'user_admin_admin': 'admin .admin .reseller_admin',
|
'account5': 'test5',
|
||||||
'user_test_tester': 'testing .admin',
|
'username5': 'tester5',
|
||||||
'user_test2_tester2': 'testing2 .admin',
|
'password5': 'testing5',
|
||||||
'user_test_tester3': 'testing3'
|
'service_prefix': 'SERVICE',
|
||||||
|
# For tempauth middleware. Update reseller_prefix
|
||||||
|
'reseller_prefix': 'AUTH, SERVICE',
|
||||||
|
'SERVICE_require_group': 'service'
|
||||||
})
|
})
|
||||||
|
|
||||||
acc1lis = eventlet.listen(('localhost', 0))
|
acc1lis = eventlet.listen(('localhost', 0))
|
||||||
@ -415,6 +418,9 @@ def setup_package():
|
|||||||
global swift_test_tenant
|
global swift_test_tenant
|
||||||
global swift_test_perm
|
global swift_test_perm
|
||||||
global swift_test_domain
|
global swift_test_domain
|
||||||
|
global swift_test_service_prefix
|
||||||
|
|
||||||
|
swift_test_service_prefix = None
|
||||||
|
|
||||||
if config:
|
if config:
|
||||||
swift_test_auth_version = str(config.get('auth_version', '1'))
|
swift_test_auth_version = str(config.get('auth_version', '1'))
|
||||||
@ -430,6 +436,10 @@ def setup_package():
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass # skip
|
pass # skip
|
||||||
|
|
||||||
|
if 'service_prefix' in config:
|
||||||
|
swift_test_service_prefix = utils.append_underscore(
|
||||||
|
config['service_prefix'])
|
||||||
|
|
||||||
if swift_test_auth_version == "1":
|
if swift_test_auth_version == "1":
|
||||||
swift_test_auth += 'v1.0'
|
swift_test_auth += 'v1.0'
|
||||||
|
|
||||||
@ -457,6 +467,13 @@ def setup_package():
|
|||||||
swift_test_key[2] = config['password3']
|
swift_test_key[2] = config['password3']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # old config, no third account tests can be run
|
pass # old config, no third account tests can be run
|
||||||
|
try:
|
||||||
|
swift_test_user[4] = '%s%s' % (
|
||||||
|
'%s:' % config['account5'], config['username5'])
|
||||||
|
swift_test_key[4] = config['password5']
|
||||||
|
swift_test_tenant[4] = config['account5']
|
||||||
|
except KeyError:
|
||||||
|
pass # no service token tests can be run
|
||||||
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
swift_test_perm[_] = swift_test_user[_]
|
swift_test_perm[_] = swift_test_user[_]
|
||||||
@ -476,8 +493,12 @@ def setup_package():
|
|||||||
swift_test_tenant[3] = config['account4']
|
swift_test_tenant[3] = config['account4']
|
||||||
swift_test_key[3] = config['password4']
|
swift_test_key[3] = config['password4']
|
||||||
swift_test_domain[3] = config['domain4']
|
swift_test_domain[3] = config['domain4']
|
||||||
|
if 'username5' in config:
|
||||||
|
swift_test_user[4] = config['username5']
|
||||||
|
swift_test_tenant[4] = config['account5']
|
||||||
|
swift_test_key[4] = config['password5']
|
||||||
|
|
||||||
for _ in range(4):
|
for _ in range(5):
|
||||||
swift_test_perm[_] = swift_test_tenant[_] + ':' \
|
swift_test_perm[_] = swift_test_tenant[_] + ':' \
|
||||||
+ swift_test_user[_]
|
+ swift_test_user[_]
|
||||||
|
|
||||||
@ -508,6 +529,14 @@ def setup_package():
|
|||||||
print >>sys.stderr, \
|
print >>sys.stderr, \
|
||||||
'SKIPPING FUNCTIONAL TESTS SPECIFIC TO AUTH VERSION 3'
|
'SKIPPING FUNCTIONAL TESTS SPECIFIC TO AUTH VERSION 3'
|
||||||
|
|
||||||
|
global skip_service_tokens
|
||||||
|
skip_service_tokens = not all([not skip, swift_test_user[4],
|
||||||
|
swift_test_key[4], swift_test_tenant[4],
|
||||||
|
swift_test_service_prefix])
|
||||||
|
if not skip and skip_service_tokens:
|
||||||
|
print >>sys.stderr, \
|
||||||
|
'SKIPPING FUNCTIONAL TESTS SPECIFIC TO SERVICE TOKENS'
|
||||||
|
|
||||||
get_cluster_info()
|
get_cluster_info()
|
||||||
|
|
||||||
|
|
||||||
@ -546,10 +575,11 @@ class InternalServerError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
url = [None, None, None, None]
|
url = [None, None, None, None, None]
|
||||||
token = [None, None, None, None]
|
token = [None, None, None, None, None]
|
||||||
parsed = [None, None, None, None]
|
service_token = [None, None, None, None, None]
|
||||||
conn = [None, None, None, None]
|
parsed = [None, None, None, None, None]
|
||||||
|
conn = [None, None, None, None, None]
|
||||||
|
|
||||||
|
|
||||||
def connection(url):
|
def connection(url):
|
||||||
@ -558,6 +588,18 @@ def connection(url):
|
|||||||
return http_connection(url)
|
return http_connection(url)
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_token(user_index, os_options):
|
||||||
|
authargs = dict(snet=False,
|
||||||
|
tenant_name=swift_test_tenant[user_index],
|
||||||
|
auth_version=swift_test_auth_version,
|
||||||
|
os_options=os_options,
|
||||||
|
insecure=insecure)
|
||||||
|
return get_auth(swift_test_auth,
|
||||||
|
swift_test_user[user_index],
|
||||||
|
swift_test_key[user_index],
|
||||||
|
**authargs)
|
||||||
|
|
||||||
|
|
||||||
def retry(func, *args, **kwargs):
|
def retry(func, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
You can use the kwargs to override:
|
You can use the kwargs to override:
|
||||||
@ -566,13 +608,17 @@ def retry(func, *args, **kwargs):
|
|||||||
'url_account' (default: matches 'use_account') - which user's storage URL
|
'url_account' (default: matches 'use_account') - which user's storage URL
|
||||||
'resource' (default: url[url_account] - URL to connect to; retry()
|
'resource' (default: url[url_account] - URL to connect to; retry()
|
||||||
will interpolate the variable :storage_url: if present
|
will interpolate the variable :storage_url: if present
|
||||||
|
'service_user' - add a service token from this user (1 indexed)
|
||||||
"""
|
"""
|
||||||
global url, token, parsed, conn
|
global url, token, service_token, parsed, conn
|
||||||
retries = kwargs.get('retries', 5)
|
retries = kwargs.get('retries', 5)
|
||||||
attempts, backoff = 0, 1
|
attempts, backoff = 0, 1
|
||||||
|
|
||||||
# use account #1 by default; turn user's 1-indexed account into 0-indexed
|
# use account #1 by default; turn user's 1-indexed account into 0-indexed
|
||||||
use_account = kwargs.pop('use_account', 1) - 1
|
use_account = kwargs.pop('use_account', 1) - 1
|
||||||
|
service_user = kwargs.pop('service_user', None)
|
||||||
|
if service_user:
|
||||||
|
service_user -= 1 # 0-index
|
||||||
|
|
||||||
# access our own account by default
|
# access our own account by default
|
||||||
url_account = kwargs.pop('url_account', use_account + 1) - 1
|
url_account = kwargs.pop('url_account', use_account + 1) - 1
|
||||||
@ -582,13 +628,8 @@ def retry(func, *args, **kwargs):
|
|||||||
attempts += 1
|
attempts += 1
|
||||||
try:
|
try:
|
||||||
if not url[use_account] or not token[use_account]:
|
if not url[use_account] or not token[use_account]:
|
||||||
url[use_account], token[use_account] = \
|
url[use_account], token[use_account] = get_url_token(
|
||||||
get_auth(swift_test_auth, swift_test_user[use_account],
|
use_account, os_options)
|
||||||
swift_test_key[use_account],
|
|
||||||
snet=False,
|
|
||||||
tenant_name=swift_test_tenant[use_account],
|
|
||||||
auth_version=swift_test_auth_version,
|
|
||||||
os_options=os_options)
|
|
||||||
parsed[use_account] = conn[use_account] = None
|
parsed[use_account] = conn[use_account] = None
|
||||||
if not parsed[use_account] or not conn[use_account]:
|
if not parsed[use_account] or not conn[use_account]:
|
||||||
parsed[use_account], conn[use_account] = \
|
parsed[use_account], conn[use_account] = \
|
||||||
@ -598,6 +639,11 @@ def retry(func, *args, **kwargs):
|
|||||||
resource = kwargs.pop('resource', '%(storage_url)s')
|
resource = kwargs.pop('resource', '%(storage_url)s')
|
||||||
template_vars = {'storage_url': url[url_account]}
|
template_vars = {'storage_url': url[url_account]}
|
||||||
parsed_result = urlparse(resource % template_vars)
|
parsed_result = urlparse(resource % template_vars)
|
||||||
|
if isinstance(service_user, int):
|
||||||
|
if not service_token[service_user]:
|
||||||
|
dummy, service_token[service_user] = get_url_token(
|
||||||
|
service_user, os_options)
|
||||||
|
kwargs['service_token'] = service_token[service_user]
|
||||||
return func(url[url_account], token[use_account],
|
return func(url[url_account], token[use_account],
|
||||||
parsed_result, conn[url_account],
|
parsed_result, conn[url_account],
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
@ -605,9 +651,12 @@ def retry(func, *args, **kwargs):
|
|||||||
if attempts > retries:
|
if attempts > retries:
|
||||||
raise
|
raise
|
||||||
parsed[use_account] = conn[use_account] = None
|
parsed[use_account] = conn[use_account] = None
|
||||||
|
if service_user:
|
||||||
|
service_token[service_user] = None
|
||||||
except AuthError:
|
except AuthError:
|
||||||
url[use_account] = token[use_account] = None
|
url[use_account] = token[use_account] = None
|
||||||
continue
|
if service_user:
|
||||||
|
service_token[service_user] = None
|
||||||
except InternalServerError:
|
except InternalServerError:
|
||||||
pass
|
pass
|
||||||
if attempts <= retries:
|
if attempts <= retries:
|
||||||
|
@ -26,8 +26,11 @@ import simplejson as json
|
|||||||
|
|
||||||
from nose import SkipTest
|
from nose import SkipTest
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
|
||||||
from swiftclient import get_auth
|
from swiftclient import get_auth
|
||||||
|
|
||||||
|
from swift.common.utils import config_true_value
|
||||||
|
|
||||||
from test import safe_repr
|
from test import safe_repr
|
||||||
|
|
||||||
|
|
||||||
@ -109,6 +112,7 @@ class Connection(object):
|
|||||||
self.auth_host = config['auth_host']
|
self.auth_host = config['auth_host']
|
||||||
self.auth_port = int(config['auth_port'])
|
self.auth_port = int(config['auth_port'])
|
||||||
self.auth_ssl = config['auth_ssl'] in ('on', 'true', 'yes', '1')
|
self.auth_ssl = config['auth_ssl'] in ('on', 'true', 'yes', '1')
|
||||||
|
self.insecure = config_true_value(config.get('insecure', 'false'))
|
||||||
self.auth_prefix = config.get('auth_prefix', '/')
|
self.auth_prefix = config.get('auth_prefix', '/')
|
||||||
self.auth_version = str(config.get('auth_version', '1'))
|
self.auth_version = str(config.get('auth_version', '1'))
|
||||||
|
|
||||||
@ -147,10 +151,11 @@ class Connection(object):
|
|||||||
auth_netloc = "%s:%d" % (self.auth_host, self.auth_port)
|
auth_netloc = "%s:%d" % (self.auth_host, self.auth_port)
|
||||||
auth_url = auth_scheme + auth_netloc + auth_path
|
auth_url = auth_scheme + auth_netloc + auth_path
|
||||||
|
|
||||||
|
authargs = dict(snet=False, tenant_name=self.account,
|
||||||
|
auth_version=self.auth_version, os_options={},
|
||||||
|
insecure=self.insecure)
|
||||||
(storage_url, storage_token) = get_auth(
|
(storage_url, storage_token) = get_auth(
|
||||||
auth_url, auth_user, self.password, snet=False,
|
auth_url, auth_user, self.password, **authargs)
|
||||||
tenant_name=self.account, auth_version=self.auth_version,
|
|
||||||
os_options={})
|
|
||||||
|
|
||||||
if not (storage_url and storage_token):
|
if not (storage_url and storage_token):
|
||||||
raise AuthenticationFailed()
|
raise AuthenticationFailed()
|
||||||
|
@ -28,8 +28,10 @@ import uuid
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import eventlet
|
import eventlet
|
||||||
from nose import SkipTest
|
from nose import SkipTest
|
||||||
|
from swift.common.http import is_success, is_client_error
|
||||||
|
|
||||||
from test.functional import normalized_urls, load_constraint, cluster_info
|
from test.functional import normalized_urls, load_constraint, cluster_info
|
||||||
|
from test.functional import check_response, retry
|
||||||
import test.functional as tf
|
import test.functional as tf
|
||||||
from test.functional.swift_test_client import Account, Connection, File, \
|
from test.functional.swift_test_client import Account, Connection, File, \
|
||||||
ResponseError
|
ResponseError
|
||||||
@ -3004,5 +3006,174 @@ class TestSloTempurlUTF8(Base2, TestSloTempurl):
|
|||||||
set_up = False
|
set_up = False
|
||||||
|
|
||||||
|
|
||||||
|
class TestServiceToken(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if tf.skip_service_tokens:
|
||||||
|
raise SkipTest
|
||||||
|
|
||||||
|
self.SET_TO_USERS_TOKEN = 1
|
||||||
|
self.SET_TO_SERVICE_TOKEN = 2
|
||||||
|
|
||||||
|
# keystoneauth and tempauth differ in allowing PUT account
|
||||||
|
# Even if keystoneauth allows it, the proxy-server uses
|
||||||
|
# allow_account_management to decide if accounts can be created
|
||||||
|
self.put_account_expect = is_client_error
|
||||||
|
if tf.swift_test_auth_version != '1':
|
||||||
|
if cluster_info.get('swift').get('allow_account_management'):
|
||||||
|
self.put_account_expect = is_success
|
||||||
|
|
||||||
|
def _scenario_generator(self):
|
||||||
|
paths = ((None, None), ('c', None), ('c', 'o'))
|
||||||
|
for path in paths:
|
||||||
|
for method in ('PUT', 'POST', 'HEAD', 'GET', 'OPTIONS'):
|
||||||
|
yield method, path[0], path[1]
|
||||||
|
for path in reversed(paths):
|
||||||
|
yield 'DELETE', path[0], path[1]
|
||||||
|
|
||||||
|
def _assert_is_authed_response(self, method, container, object, resp):
|
||||||
|
resp.read()
|
||||||
|
expect = is_success
|
||||||
|
if method == 'DELETE' and not container:
|
||||||
|
expect = is_client_error
|
||||||
|
if method == 'PUT' and not container:
|
||||||
|
expect = self.put_account_expect
|
||||||
|
self.assertTrue(expect(resp.status), 'Unexpected %s for %s %s %s'
|
||||||
|
% (resp.status, method, container, object))
|
||||||
|
|
||||||
|
def _assert_not_authed_response(self, method, container, object, resp):
|
||||||
|
resp.read()
|
||||||
|
expect = is_client_error
|
||||||
|
if method == 'OPTIONS':
|
||||||
|
expect = is_success
|
||||||
|
self.assertTrue(expect(resp.status), 'Unexpected %s for %s %s %s'
|
||||||
|
% (resp.status, method, container, object))
|
||||||
|
|
||||||
|
def prepare_request(self, method, use_service_account=False,
|
||||||
|
container=None, obj=None, body=None, headers=None,
|
||||||
|
x_auth_token=None,
|
||||||
|
x_service_token=None, dbg=False):
|
||||||
|
"""
|
||||||
|
Setup for making the request
|
||||||
|
|
||||||
|
When retry() calls the do_request() function, it calls it the
|
||||||
|
test user's token, the parsed path, a connection and (optionally)
|
||||||
|
a token from the test service user. We save options here so that
|
||||||
|
do_request() can make the appropriate request.
|
||||||
|
|
||||||
|
:param method: The operation (e.g'. 'HEAD')
|
||||||
|
:param use_service_account: Optional. Set True to change the path to
|
||||||
|
be the service account
|
||||||
|
:param container: Optional. Adds a container name to the path
|
||||||
|
:param obj: Optional. Adds an object name to the path
|
||||||
|
:param body: Optional. Adds a body (string) in the request
|
||||||
|
:param headers: Optional. Adds additional headers.
|
||||||
|
:param x_auth_token: Optional. Default is SET_TO_USERS_TOKEN. One of:
|
||||||
|
SET_TO_USERS_TOKEN Put the test user's token in
|
||||||
|
X-Auth-Token
|
||||||
|
SET_TO_SERVICE_TOKEN Put the service token in X-Auth-Token
|
||||||
|
:param x_service_token: Optional. Default is to not set X-Service-Token
|
||||||
|
to any value. If specified, is one of following:
|
||||||
|
SET_TO_USERS_TOKEN Put the test user's token in
|
||||||
|
X-Service-Token
|
||||||
|
SET_TO_SERVICE_TOKEN Put the service token in
|
||||||
|
X-Service-Token
|
||||||
|
:param dbg: Optional. Set true to check request arguments
|
||||||
|
"""
|
||||||
|
self.method = method
|
||||||
|
self.use_service_account = use_service_account
|
||||||
|
self.container = container
|
||||||
|
self.obj = obj
|
||||||
|
self.body = body
|
||||||
|
self.headers = headers
|
||||||
|
if x_auth_token:
|
||||||
|
self.x_auth_token = x_auth_token
|
||||||
|
else:
|
||||||
|
self.x_auth_token = self.SET_TO_USERS_TOKEN
|
||||||
|
self.x_service_token = x_service_token
|
||||||
|
self.dbg = dbg
|
||||||
|
|
||||||
|
def do_request(self, url, token, parsed, conn, service_token=''):
|
||||||
|
if self.use_service_account:
|
||||||
|
path = self._service_account(parsed.path)
|
||||||
|
else:
|
||||||
|
path = parsed.path
|
||||||
|
if self.container:
|
||||||
|
path += '/%s' % self.container
|
||||||
|
if self.obj:
|
||||||
|
path += '/%s' % self.obj
|
||||||
|
headers = {}
|
||||||
|
if self.body:
|
||||||
|
headers.update({'Content-Length': len(self.body)})
|
||||||
|
if self.headers:
|
||||||
|
headers.update(self.headers)
|
||||||
|
if self.x_auth_token == self.SET_TO_USERS_TOKEN:
|
||||||
|
headers.update({'X-Auth-Token': token})
|
||||||
|
elif self.x_auth_token == self.SET_TO_SERVICE_TOKEN:
|
||||||
|
headers.update({'X-Auth-Token': service_token})
|
||||||
|
if self.x_service_token == self.SET_TO_USERS_TOKEN:
|
||||||
|
headers.update({'X-Service-Token': token})
|
||||||
|
elif self.x_service_token == self.SET_TO_SERVICE_TOKEN:
|
||||||
|
headers.update({'X-Service-Token': service_token})
|
||||||
|
if self.dbg:
|
||||||
|
print('DEBUG: conn.request: method:%s path:%s'
|
||||||
|
' body:%s headers:%s' % (self.method, path, self.body,
|
||||||
|
headers))
|
||||||
|
conn.request(self.method, path, self.body, headers=headers)
|
||||||
|
return check_response(conn)
|
||||||
|
|
||||||
|
def _service_account(self, path):
|
||||||
|
parts = path.split('/', 3)
|
||||||
|
account = parts[2]
|
||||||
|
try:
|
||||||
|
project_id = account[account.index('_') + 1:]
|
||||||
|
except ValueError:
|
||||||
|
project_id = account
|
||||||
|
parts[2] = '%s%s' % (tf.swift_test_service_prefix, project_id)
|
||||||
|
return '/'.join(parts)
|
||||||
|
|
||||||
|
def test_user_access_own_auth_account(self):
|
||||||
|
# This covers ground tested elsewhere (tests a user doing HEAD
|
||||||
|
# on own account). However, if this fails, none of the remaining
|
||||||
|
# tests will work
|
||||||
|
self.prepare_request('HEAD')
|
||||||
|
resp = retry(self.do_request)
|
||||||
|
resp.read()
|
||||||
|
self.assert_(resp.status in (200, 204), resp.status)
|
||||||
|
|
||||||
|
def test_user_cannot_access_service_account(self):
|
||||||
|
for method, container, obj in self._scenario_generator():
|
||||||
|
self.prepare_request(method, use_service_account=True,
|
||||||
|
container=container, obj=obj)
|
||||||
|
resp = retry(self.do_request)
|
||||||
|
self._assert_not_authed_response(method, container, obj, resp)
|
||||||
|
|
||||||
|
def test_service_user_denied_with_x_auth_token(self):
|
||||||
|
for method, container, obj in self._scenario_generator():
|
||||||
|
self.prepare_request(method, use_service_account=True,
|
||||||
|
container=container, obj=obj,
|
||||||
|
x_auth_token=self.SET_TO_SERVICE_TOKEN)
|
||||||
|
resp = retry(self.do_request, service_user=5)
|
||||||
|
self._assert_not_authed_response(method, container, obj, resp)
|
||||||
|
|
||||||
|
def test_service_user_denied_with_x_service_token(self):
|
||||||
|
for method, container, obj in self._scenario_generator():
|
||||||
|
self.prepare_request(method, use_service_account=True,
|
||||||
|
container=container, obj=obj,
|
||||||
|
x_auth_token=self.SET_TO_SERVICE_TOKEN,
|
||||||
|
x_service_token=self.SET_TO_SERVICE_TOKEN)
|
||||||
|
resp = retry(self.do_request, service_user=5)
|
||||||
|
self._assert_not_authed_response(method, container, obj, resp)
|
||||||
|
|
||||||
|
def test_user_plus_service_can_access_service_account(self):
|
||||||
|
for method, container, obj in self._scenario_generator():
|
||||||
|
self.prepare_request(method, use_service_account=True,
|
||||||
|
container=container, obj=obj,
|
||||||
|
x_auth_token=self.SET_TO_USERS_TOKEN,
|
||||||
|
x_service_token=self.SET_TO_SERVICE_TOKEN)
|
||||||
|
resp = retry(self.do_request, service_user=5)
|
||||||
|
self._assert_is_authed_response(method, container, obj, resp)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -33,6 +33,31 @@ password3 = testing3
|
|||||||
#password4 = testing4
|
#password4 = testing4
|
||||||
#domain4 = test-domain
|
#domain4 = test-domain
|
||||||
|
|
||||||
|
# Fifth user is required for service token-specific tests.
|
||||||
|
# The account must be different than the primary test account
|
||||||
|
# The user must not have a group (tempauth) or role (keystoneauth) on
|
||||||
|
# the primary test account. The user must have a group/role that is unique
|
||||||
|
# and not given to the primary tester and is specified in the options
|
||||||
|
# <prefix>_require_group (tempauth) or <prefix>_service_roles (keystoneauth).
|
||||||
|
#account5 = service
|
||||||
|
#username5 = tester5
|
||||||
|
#password5 = testing5
|
||||||
|
|
||||||
|
# The service_prefix option is used for service token-specific tests.
|
||||||
|
# If service_prefix or username5 above is not supplied, the tests are skipped.
|
||||||
|
# To set the value and enable the service token tests, look at the
|
||||||
|
# reseller_prefix option in /etc/swift/proxy-server.conf. There must be at
|
||||||
|
# least two prefixes. If not, add a prefix as follows (where we add SERVICE):
|
||||||
|
# reseller_prefix = AUTH, SERVICE
|
||||||
|
# The service_prefix must match the <prefix> used in <prefix>_require_group
|
||||||
|
# (tempauth) or <prefix>_service_roles (keystoneauth); for example:
|
||||||
|
# SERVICE_require_group = service
|
||||||
|
# SERVICE_service_roles = service
|
||||||
|
# Note: Do not enable service token tests if the first prefix in
|
||||||
|
# reseller_prefix is the empty prefix AND the primary functional test
|
||||||
|
# account contains an underscore.
|
||||||
|
#service_prefix = SERVICE
|
||||||
|
|
||||||
collate = C
|
collate = C
|
||||||
|
|
||||||
# Only necessary if a pre-existing server uses self-signed certificate
|
# Only necessary if a pre-existing server uses self-signed certificate
|
||||||
|
@ -18,6 +18,7 @@ import unittest
|
|||||||
from swift.common.middleware import keystoneauth
|
from swift.common.middleware import keystoneauth
|
||||||
from swift.common.swob import Request, Response
|
from swift.common.swob import Request, Response
|
||||||
from swift.common.http import HTTP_FORBIDDEN
|
from swift.common.http import HTTP_FORBIDDEN
|
||||||
|
from swift.common.utils import split_path
|
||||||
from swift.proxy.controllers.base import _get_cache_key
|
from swift.proxy.controllers.base import _get_cache_key
|
||||||
from test.unit import FakeLogger
|
from test.unit import FakeLogger
|
||||||
|
|
||||||
@ -31,6 +32,45 @@ def _fake_token_info(version='2'):
|
|||||||
return {'token': 'fake_value'}
|
return {'token': 'fake_value'}
|
||||||
|
|
||||||
|
|
||||||
|
def operator_roles(test_auth):
|
||||||
|
# Return copy -- not a reference
|
||||||
|
return list(test_auth.account_rules[test_auth.reseller_prefixes[0]].get(
|
||||||
|
'operator_roles'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_for_tenant(test_auth, tenant_id):
|
||||||
|
"""Convenience function reduces unit test churn"""
|
||||||
|
return '%s%s' % (test_auth.reseller_prefixes[0], tenant_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_identity_headers(status='Confirmed', tenant_id='1',
|
||||||
|
tenant_name='acct', project_domain_name='domA',
|
||||||
|
project_domain_id='99',
|
||||||
|
user_name='usr', user_id='42',
|
||||||
|
user_domain_name='domA', user_domain_id='99',
|
||||||
|
role='admin',
|
||||||
|
service_role=None):
|
||||||
|
if role is None:
|
||||||
|
role = []
|
||||||
|
if isinstance(role, list):
|
||||||
|
role = ','.join(role)
|
||||||
|
res = dict(X_IDENTITY_STATUS=status,
|
||||||
|
X_TENANT_ID=tenant_id,
|
||||||
|
X_TENANT_NAME=tenant_name,
|
||||||
|
X_PROJECT_ID=tenant_id,
|
||||||
|
X_PROJECT_NAME=tenant_name,
|
||||||
|
X_PROJECT_DOMAIN_ID=project_domain_id,
|
||||||
|
X_PROJECT_DOMAIN_NAME=project_domain_name,
|
||||||
|
X_ROLES=role,
|
||||||
|
X_USER_NAME=user_name,
|
||||||
|
X_USER_ID=user_id,
|
||||||
|
X_USER_DOMAIN_NAME=user_domain_name,
|
||||||
|
X_USER_DOMAIN_ID=user_domain_id)
|
||||||
|
if service_role:
|
||||||
|
res.update(X_SERVICE_ROLES=service_role)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class FakeApp(object):
|
class FakeApp(object):
|
||||||
def __init__(self, status_headers_body_iter=None):
|
def __init__(self, status_headers_body_iter=None):
|
||||||
self.calls = 0
|
self.calls = 0
|
||||||
@ -61,35 +101,16 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
|
|
||||||
def _make_request(self, path=None, headers=None, **kwargs):
|
def _make_request(self, path=None, headers=None, **kwargs):
|
||||||
if not path:
|
if not path:
|
||||||
path = '/v1/%s/c/o' % self.test_auth._get_account_for_tenant('foo')
|
path = '/v1/%s/c/o' % get_account_for_tenant(self.test_auth, 'foo')
|
||||||
return Request.blank(path, headers=headers, **kwargs)
|
return Request.blank(path, headers=headers, **kwargs)
|
||||||
|
|
||||||
def _get_identity_headers(self, status='Confirmed', tenant_id='1',
|
|
||||||
tenant_name='acct', project_domain_name='domA',
|
|
||||||
project_domain_id='99',
|
|
||||||
user_name='usr', user_id='42',
|
|
||||||
user_domain_name='domA', user_domain_id='99',
|
|
||||||
role='admin'):
|
|
||||||
return dict(X_IDENTITY_STATUS=status,
|
|
||||||
X_TENANT_ID=tenant_id,
|
|
||||||
X_TENANT_NAME=tenant_name,
|
|
||||||
X_PROJECT_ID=tenant_id,
|
|
||||||
X_PROJECT_NAME=tenant_name,
|
|
||||||
X_PROJECT_DOMAIN_ID=project_domain_id,
|
|
||||||
X_PROJECT_DOMAIN_NAME=project_domain_name,
|
|
||||||
X_ROLES=role,
|
|
||||||
X_USER_NAME=user_name,
|
|
||||||
X_USER_ID=user_id,
|
|
||||||
X_USER_DOMAIN_NAME=user_domain_name,
|
|
||||||
X_USER_DOMAIN_ID=user_domain_id)
|
|
||||||
|
|
||||||
def _get_successful_middleware(self):
|
def _get_successful_middleware(self):
|
||||||
response_iter = iter([('200 OK', {}, '')])
|
response_iter = iter([('200 OK', {}, '')])
|
||||||
return keystoneauth.filter_factory({})(FakeApp(response_iter))
|
return keystoneauth.filter_factory({})(FakeApp(response_iter))
|
||||||
|
|
||||||
def test_invalid_request_authorized(self):
|
def test_invalid_request_authorized(self):
|
||||||
role = self.test_auth.reseller_admin_role
|
role = self.test_auth.reseller_admin_role
|
||||||
headers = self._get_identity_headers(role=role)
|
headers = get_identity_headers(role=role)
|
||||||
req = self._make_request('/', headers=headers)
|
req = self._make_request('/', headers=headers)
|
||||||
resp = req.get_response(self._get_successful_middleware())
|
resp = req.get_response(self._get_successful_middleware())
|
||||||
self.assertEqual(resp.status_int, 404)
|
self.assertEqual(resp.status_int, 404)
|
||||||
@ -101,20 +122,20 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
|
|
||||||
def test_confirmed_identity_is_authorized(self):
|
def test_confirmed_identity_is_authorized(self):
|
||||||
role = self.test_auth.reseller_admin_role
|
role = self.test_auth.reseller_admin_role
|
||||||
headers = self._get_identity_headers(role=role)
|
headers = get_identity_headers(role=role)
|
||||||
req = self._make_request('/v1/AUTH_acct/c', headers)
|
req = self._make_request('/v1/AUTH_acct/c', headers)
|
||||||
resp = req.get_response(self._get_successful_middleware())
|
resp = req.get_response(self._get_successful_middleware())
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
def test_detect_reseller_request(self):
|
def test_detect_reseller_request(self):
|
||||||
role = self.test_auth.reseller_admin_role
|
role = self.test_auth.reseller_admin_role
|
||||||
headers = self._get_identity_headers(role=role)
|
headers = get_identity_headers(role=role)
|
||||||
req = self._make_request('/v1/AUTH_acct/c', headers)
|
req = self._make_request('/v1/AUTH_acct/c', headers)
|
||||||
req.get_response(self._get_successful_middleware())
|
req.get_response(self._get_successful_middleware())
|
||||||
self.assertTrue(req.environ.get('reseller_request'))
|
self.assertTrue(req.environ.get('reseller_request'))
|
||||||
|
|
||||||
def test_confirmed_identity_is_not_authorized(self):
|
def test_confirmed_identity_is_not_authorized(self):
|
||||||
headers = self._get_identity_headers()
|
headers = get_identity_headers()
|
||||||
req = self._make_request('/v1/AUTH_acct/c', headers)
|
req = self._make_request('/v1/AUTH_acct/c', headers)
|
||||||
resp = req.get_response(self.test_auth)
|
resp = req.get_response(self.test_auth)
|
||||||
self.assertEqual(resp.status_int, 403)
|
self.assertEqual(resp.status_int, 403)
|
||||||
@ -141,17 +162,17 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
conf = {'reseller_prefix': ''}
|
conf = {'reseller_prefix': ''}
|
||||||
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
account = tenant_id = 'foo'
|
account = tenant_id = 'foo'
|
||||||
self.assertTrue(test_auth._reseller_check(account, tenant_id))
|
self.assertTrue(test_auth._account_matches_tenant(account, tenant_id))
|
||||||
|
|
||||||
def test_reseller_prefix_added_underscore(self):
|
def test_reseller_prefix_added_underscore(self):
|
||||||
conf = {'reseller_prefix': 'AUTH'}
|
conf = {'reseller_prefix': 'AUTH'}
|
||||||
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
self.assertEqual(test_auth.reseller_prefix, "AUTH_")
|
self.assertEqual(test_auth.reseller_prefixes[0], "AUTH_")
|
||||||
|
|
||||||
def test_reseller_prefix_not_added_double_underscores(self):
|
def test_reseller_prefix_not_added_double_underscores(self):
|
||||||
conf = {'reseller_prefix': 'AUTH_'}
|
conf = {'reseller_prefix': 'AUTH_'}
|
||||||
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
self.assertEqual(test_auth.reseller_prefix, "AUTH_")
|
self.assertEqual(test_auth.reseller_prefixes[0], "AUTH_")
|
||||||
|
|
||||||
def test_override_asked_for_but_not_allowed(self):
|
def test_override_asked_for_but_not_allowed(self):
|
||||||
conf = {'allow_overrides': 'false'}
|
conf = {'allow_overrides': 'false'}
|
||||||
@ -182,10 +203,10 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
def test_identified_options_allowed(self):
|
def test_identified_options_allowed(self):
|
||||||
headers = self._get_identity_headers()
|
headers = get_identity_headers()
|
||||||
headers['REQUEST_METHOD'] = 'OPTIONS'
|
headers['REQUEST_METHOD'] = 'OPTIONS'
|
||||||
req = self._make_request('/v1/AUTH_account',
|
req = self._make_request('/v1/AUTH_account',
|
||||||
headers=self._get_identity_headers(),
|
headers=get_identity_headers(),
|
||||||
environ={'REQUEST_METHOD': 'OPTIONS'})
|
environ={'REQUEST_METHOD': 'OPTIONS'})
|
||||||
resp = req.get_response(self._get_successful_middleware())
|
resp = req.get_response(self._get_successful_middleware())
|
||||||
self.assertEqual(resp.status_int, 200)
|
self.assertEqual(resp.status_int, 200)
|
||||||
@ -200,9 +221,9 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
def test_project_domain_id_sysmeta_set(self):
|
def test_project_domain_id_sysmeta_set(self):
|
||||||
proj_id = '12345678'
|
proj_id = '12345678'
|
||||||
proj_domain_id = '13'
|
proj_domain_id = '13'
|
||||||
headers = self._get_identity_headers(tenant_id=proj_id,
|
headers = get_identity_headers(tenant_id=proj_id,
|
||||||
project_domain_id=proj_domain_id)
|
project_domain_id=proj_domain_id)
|
||||||
account = self.test_auth._get_account_for_tenant(proj_id)
|
account = get_account_for_tenant(self.test_auth, proj_id)
|
||||||
path = '/v1/' + account
|
path = '/v1/' + account
|
||||||
# fake cached account info
|
# fake cached account info
|
||||||
_, info_key = _get_cache_key(account, None)
|
_, info_key = _get_cache_key(account, None)
|
||||||
@ -228,10 +249,10 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
def test_project_domain_id_sysmeta_set_to_unknown(self):
|
def test_project_domain_id_sysmeta_set_to_unknown(self):
|
||||||
proj_id = '12345678'
|
proj_id = '12345678'
|
||||||
# token scoped to a different project
|
# token scoped to a different project
|
||||||
headers = self._get_identity_headers(tenant_id='87654321',
|
headers = get_identity_headers(tenant_id='87654321',
|
||||||
project_domain_id='default',
|
project_domain_id='default',
|
||||||
role='reselleradmin')
|
role='reselleradmin')
|
||||||
account = self.test_auth._get_account_for_tenant(proj_id)
|
account = get_account_for_tenant(self.test_auth, proj_id)
|
||||||
path = '/v1/' + account
|
path = '/v1/' + account
|
||||||
# fake cached account info
|
# fake cached account info
|
||||||
_, info_key = _get_cache_key(account, None)
|
_, info_key = _get_cache_key(account, None)
|
||||||
@ -252,8 +273,8 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
|
|
||||||
def test_project_domain_id_sysmeta_not_set(self):
|
def test_project_domain_id_sysmeta_not_set(self):
|
||||||
proj_id = '12345678'
|
proj_id = '12345678'
|
||||||
headers = self._get_identity_headers(tenant_id=proj_id, role='admin')
|
headers = get_identity_headers(tenant_id=proj_id, role='admin')
|
||||||
account = self.test_auth._get_account_for_tenant(proj_id)
|
account = get_account_for_tenant(self.test_auth, proj_id)
|
||||||
path = '/v1/' + account
|
path = '/v1/' + account
|
||||||
_, info_key = _get_cache_key(account, None)
|
_, info_key = _get_cache_key(account, None)
|
||||||
# v2 token
|
# v2 token
|
||||||
@ -273,9 +294,9 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
def test_project_domain_id_sysmeta_set_unknown_with_v2(self):
|
def test_project_domain_id_sysmeta_set_unknown_with_v2(self):
|
||||||
proj_id = '12345678'
|
proj_id = '12345678'
|
||||||
# token scoped to a different project
|
# token scoped to a different project
|
||||||
headers = self._get_identity_headers(tenant_id='87654321',
|
headers = get_identity_headers(tenant_id='87654321',
|
||||||
role='reselleradmin')
|
role='reselleradmin')
|
||||||
account = self.test_auth._get_account_for_tenant(proj_id)
|
account = get_account_for_tenant(self.test_auth, proj_id)
|
||||||
path = '/v1/' + account
|
path = '/v1/' + account
|
||||||
_, info_key = _get_cache_key(account, None)
|
_, info_key = _get_cache_key(account, None)
|
||||||
# v2 token
|
# v2 token
|
||||||
@ -295,6 +316,171 @@ class SwiftAuth(unittest.TestCase):
|
|||||||
UNKNOWN_ID)
|
UNKNOWN_ID)
|
||||||
|
|
||||||
|
|
||||||
|
class SwiftAuthMultiple(SwiftAuth):
|
||||||
|
"""Runs same tests as SwiftAuth with multiple reseller prefixes
|
||||||
|
|
||||||
|
Runs SwiftAuth tests while a second reseller prefix item exists.
|
||||||
|
Validates that there is no regression against the original
|
||||||
|
single prefix configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_auth = keystoneauth.filter_factory(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2'})(FakeApp())
|
||||||
|
self.test_auth.logger = FakeLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceTokenFunctionality(unittest.TestCase):
|
||||||
|
|
||||||
|
def _make_authed_request(self, conf, project_id, path, method='GET',
|
||||||
|
user_role='admin', service_role=None):
|
||||||
|
"""Make a request with keystoneauth as auth
|
||||||
|
|
||||||
|
By default, acts as though the user had presented a token
|
||||||
|
containing the 'admin' role in X-Auth-Token scoped to the specified
|
||||||
|
project_id.
|
||||||
|
|
||||||
|
:param conf: configuration for keystoneauth
|
||||||
|
:param project_id: the project_id of the token
|
||||||
|
:param path: the path of the request
|
||||||
|
:param method: the method (defaults to GET)
|
||||||
|
:param user_role: the role of X-Auth-Token (defaults to 'admin')
|
||||||
|
:param service_role: the role in X-Service-Token (defaults to none)
|
||||||
|
|
||||||
|
:returns: response object
|
||||||
|
"""
|
||||||
|
headers = get_identity_headers(tenant_id=project_id,
|
||||||
|
role=user_role,
|
||||||
|
service_role=service_role)
|
||||||
|
(version, account, _junk, _junk) = split_path(path, 2, 4, True)
|
||||||
|
_, info_key = _get_cache_key(account, None)
|
||||||
|
env = {info_key: {'status': 0, 'sysmeta': {}},
|
||||||
|
'keystone.token_info': _fake_token_info(version='2')}
|
||||||
|
req = Request.blank(path, environ=env, headers=headers)
|
||||||
|
req.method = method
|
||||||
|
fake_app = FakeApp(iter([('200 OK', {}, '')]))
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(fake_app)
|
||||||
|
resp = req.get_response(test_auth)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def test_unknown_prefix(self):
|
||||||
|
resp = self._make_authed_request({}, '12345678', '/v1/BLAH_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2'}, '12345678', '/v1/BLAH_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_authed_for_path_single(self):
|
||||||
|
resp = self._make_authed_request({}, '12345678', '/v1/AUTH_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678/c')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678',
|
||||||
|
user_role='ResellerAdmin')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_anything',
|
||||||
|
user_role='ResellerAdmin')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_path_single(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_789')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678',
|
||||||
|
user_role='something_else')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_authed_for_primary_path_multiple(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/AUTH_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_with_only_operator_role(self):
|
||||||
|
# User only presents X-Auth-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
# User puts token in X-Service-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678',
|
||||||
|
user_role='', service_role='admin')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
# User puts token in both X-Auth-Token and X-Service-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678',
|
||||||
|
user_role='admin', service_role='admin')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_authed_for_second_path_with_operator_role_and_service(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', service_role='service')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_with_only_service(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', user_role='something_else',
|
||||||
|
service_role='service')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_for_service_user(self):
|
||||||
|
# User presents token with 'service' role in X-Auth-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', user_role='service')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
# User presents token with 'service' role in X-Auth-Token
|
||||||
|
# and also in X-Service-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', user_role='service',
|
||||||
|
service_role='service')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_delete_denied_for_second_path(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', service_role='service',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_delete_of_second_path_by_reseller_admin(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_service_roles': 'service'},
|
||||||
|
'12345678', '/v1/PRE2_12345678', user_role='ResellerAdmin',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
|
||||||
class BaseTestAuthorize(unittest.TestCase):
|
class BaseTestAuthorize(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.test_auth = keystoneauth.filter_factory({})(FakeApp())
|
self.test_auth = keystoneauth.filter_factory({})(FakeApp())
|
||||||
@ -306,8 +492,8 @@ class BaseTestAuthorize(unittest.TestCase):
|
|||||||
def _get_account(self, identity=None):
|
def _get_account(self, identity=None):
|
||||||
if not identity:
|
if not identity:
|
||||||
identity = self._get_identity()
|
identity = self._get_identity()
|
||||||
return self.test_auth._get_account_for_tenant(
|
return get_account_for_tenant(self.test_auth,
|
||||||
identity['HTTP_X_TENANT_ID'])
|
identity['HTTP_X_TENANT_ID'])
|
||||||
|
|
||||||
def _get_identity(self, tenant_id='tenant_id', tenant_name='tenant_name',
|
def _get_identity(self, tenant_id='tenant_id', tenant_name='tenant_name',
|
||||||
user_id='user_id', user_name='user_name', roles=None,
|
user_id='user_id', user_name='user_name', roles=None,
|
||||||
@ -393,13 +579,13 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
self.assertTrue(req.environ.get('swift_owner'))
|
self.assertTrue(req.environ.get('swift_owner'))
|
||||||
|
|
||||||
def test_authorize_succeeds_as_owner_for_operator_role(self):
|
def test_authorize_succeeds_as_owner_for_operator_role(self):
|
||||||
roles = self.test_auth.operator_roles.split(',')
|
roles = operator_roles(self.test_auth)
|
||||||
identity = self._get_identity(roles=roles)
|
identity = self._get_identity(roles=roles)
|
||||||
req = self._check_authenticate(identity=identity)
|
req = self._check_authenticate(identity=identity)
|
||||||
self.assertTrue(req.environ.get('swift_owner'))
|
self.assertTrue(req.environ.get('swift_owner'))
|
||||||
|
|
||||||
def test_authorize_succeeds_as_owner_for_insensitive_operator_role(self):
|
def test_authorize_succeeds_as_owner_for_insensitive_operator_role(self):
|
||||||
roles = [r.upper() for r in self.test_auth.operator_roles.split(',')]
|
roles = [r.upper() for r in operator_roles(self.test_auth)]
|
||||||
identity = self._get_identity(roles=roles)
|
identity = self._get_identity(roles=roles)
|
||||||
req = self._check_authenticate(identity=identity)
|
req = self._check_authenticate(identity=identity)
|
||||||
self.assertTrue(req.environ.get('swift_owner'))
|
self.assertTrue(req.environ.get('swift_owner'))
|
||||||
@ -570,7 +756,7 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
'tenantID:userID')
|
'tenantID:userID')
|
||||||
|
|
||||||
def test_delete_own_account_not_allowed(self):
|
def test_delete_own_account_not_allowed(self):
|
||||||
roles = self.test_auth.operator_roles.split(',')
|
roles = operator_roles(self.test_auth)
|
||||||
identity = self._get_identity(roles=roles)
|
identity = self._get_identity(roles=roles)
|
||||||
account = self._get_account(identity)
|
account = self._get_account(identity)
|
||||||
self._check_authenticate(account=account,
|
self._check_authenticate(account=account,
|
||||||
@ -597,7 +783,7 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
self.test_auth(the_env, fake_start_response)
|
self.test_auth(the_env, fake_start_response)
|
||||||
|
|
||||||
subreq = Request.blank(
|
subreq = Request.blank(
|
||||||
'/v1/%s/c/o' % self.test_auth._get_account_for_tenant('test'))
|
'/v1/%s/c/o' % get_account_for_tenant(self.test_auth, 'test'))
|
||||||
subreq.environ.update(
|
subreq.environ.update(
|
||||||
self._get_identity(tenant_id='test', roles=['got_erased']))
|
self._get_identity(tenant_id='test', roles=['got_erased']))
|
||||||
|
|
||||||
@ -671,6 +857,7 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
def test_integral_keystone_identity(self):
|
def test_integral_keystone_identity(self):
|
||||||
user = ('U_ID', 'U_NAME')
|
user = ('U_ID', 'U_NAME')
|
||||||
roles = ('ROLE1', 'ROLE2')
|
roles = ('ROLE1', 'ROLE2')
|
||||||
|
service_roles = ('ROLE3', 'ROLE4')
|
||||||
project = ('P_ID', 'P_NAME')
|
project = ('P_ID', 'P_NAME')
|
||||||
user_domain = ('UD_ID', 'UD_NAME')
|
user_domain = ('UD_ID', 'UD_NAME')
|
||||||
project_domain = ('PD_ID', 'PD_NAME')
|
project_domain = ('PD_ID', 'PD_NAME')
|
||||||
@ -699,6 +886,7 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
expected = {'user': user,
|
expected = {'user': user,
|
||||||
'tenant': project,
|
'tenant': project,
|
||||||
'roles': list(roles),
|
'roles': list(roles),
|
||||||
|
'service_roles': [],
|
||||||
'user_domain': (None, None),
|
'user_domain': (None, None),
|
||||||
'project_domain': (None, None),
|
'project_domain': (None, None),
|
||||||
'auth_version': 0}
|
'auth_version': 0}
|
||||||
@ -710,6 +898,7 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
expected = {'user': user,
|
expected = {'user': user,
|
||||||
'tenant': project,
|
'tenant': project,
|
||||||
'roles': list(roles),
|
'roles': list(roles),
|
||||||
|
'service_roles': [],
|
||||||
'user_domain': (None, None),
|
'user_domain': (None, None),
|
||||||
'project_domain': (None, None),
|
'project_domain': (None, None),
|
||||||
'auth_version': 2}
|
'auth_version': 2}
|
||||||
@ -721,6 +910,19 @@ class TestAuthorize(BaseTestAuthorize):
|
|||||||
expected = {'user': user,
|
expected = {'user': user,
|
||||||
'tenant': project,
|
'tenant': project,
|
||||||
'roles': list(roles),
|
'roles': list(roles),
|
||||||
|
'service_roles': [],
|
||||||
|
'user_domain': user_domain,
|
||||||
|
'project_domain': project_domain,
|
||||||
|
'auth_version': 3}
|
||||||
|
data = self.test_auth._integral_keystone_identity(req.environ)
|
||||||
|
self.assertEquals(expected, data)
|
||||||
|
|
||||||
|
# service token in environ
|
||||||
|
req.headers.update({'X-Service-Roles': '%s,%s' % service_roles})
|
||||||
|
expected = {'user': user,
|
||||||
|
'tenant': project,
|
||||||
|
'roles': list(roles),
|
||||||
|
'service_roles': list(service_roles),
|
||||||
'user_domain': user_domain,
|
'user_domain': user_domain,
|
||||||
'project_domain': project_domain,
|
'project_domain': project_domain,
|
||||||
'auth_version': 3}
|
'auth_version': 3}
|
||||||
@ -764,7 +966,7 @@ class TestIsNameAllowedInACL(BaseTestAuthorize):
|
|||||||
scoped='account'):
|
scoped='account'):
|
||||||
project_name = 'foo'
|
project_name = 'foo'
|
||||||
account_id = '12345678'
|
account_id = '12345678'
|
||||||
account = self.test_auth._get_account_for_tenant(account_id)
|
account = get_account_for_tenant(self.test_auth, account_id)
|
||||||
parts = ('v1', account, None, None)
|
parts = ('v1', account, None, None)
|
||||||
path = '/%s/%s' % parts[0:2]
|
path = '/%s/%s' % parts[0:2]
|
||||||
|
|
||||||
@ -1175,5 +1377,106 @@ class TestSetProjectDomain(BaseTestAuthorize):
|
|||||||
sysmeta_project_domain_id='test_id')
|
sysmeta_project_domain_id='test_id')
|
||||||
|
|
||||||
|
|
||||||
|
class ResellerInInfo(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.default_rules = {'operator_roles': ['admin', 'swiftoperator'],
|
||||||
|
'service_roles': []}
|
||||||
|
|
||||||
|
def test_defaults(self):
|
||||||
|
test_auth = keystoneauth.filter_factory({})(FakeApp())
|
||||||
|
self.assertEqual(test_auth.account_rules['AUTH_'], self.default_rules)
|
||||||
|
|
||||||
|
def test_multiple(self):
|
||||||
|
conf = {"reseller_prefix": "AUTH, '', PRE2"}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(test_auth.account_rules['AUTH_'], self.default_rules)
|
||||||
|
self.assertEqual(test_auth.account_rules[''], self.default_rules)
|
||||||
|
self.assertEqual(test_auth.account_rules['PRE2_'], self.default_rules)
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixAccount(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_default(self):
|
||||||
|
conf = {}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(get_account_for_tenant(test_auth,
|
||||||
|
'1234'), 'AUTH_1234')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '1234'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '5678'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'JUNK_1234', '1234'))
|
||||||
|
|
||||||
|
def test_same_as_default(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH'}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(get_account_for_tenant(test_auth,
|
||||||
|
'1234'), 'AUTH_1234')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '1234'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '5678'))
|
||||||
|
|
||||||
|
def test_blank_reseller(self):
|
||||||
|
conf = {'reseller_prefix': ''}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(get_account_for_tenant(test_auth,
|
||||||
|
'1234'), '1234')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'1234'), '')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), '') # yes, it should return ''
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'1234', '1234'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'1234', '5678'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'JUNK_1234', '1234'))
|
||||||
|
|
||||||
|
def test_multiple_resellers(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2'}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(get_account_for_tenant(test_auth,
|
||||||
|
'1234'), 'AUTH_1234')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '1234'))
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'PRE2_1234', '1234'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'AUTH_1234', '5678'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'PRE2_1234', '5678'))
|
||||||
|
|
||||||
|
def test_blank_plus_other_reseller(self):
|
||||||
|
conf = {'reseller_prefix': " '', PRE2"}
|
||||||
|
test_auth = keystoneauth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEqual(get_account_for_tenant(test_auth,
|
||||||
|
'1234'), '1234')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix(
|
||||||
|
'PRE2_1234'), 'PRE2_')
|
||||||
|
self.assertEqual(test_auth._get_account_prefix('JUNK_1234'), '')
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'1234', '1234'))
|
||||||
|
self.assertTrue(test_auth._account_matches_tenant(
|
||||||
|
'PRE2_1234', '1234'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'1234', '5678'))
|
||||||
|
self.assertFalse(test_auth._account_matches_tenant(
|
||||||
|
'PRE2_1234', '5678'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2011 OpenStack Foundation
|
# Copyright (c) 2011-2015 OpenStack Foundation
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -119,10 +119,26 @@ class TestAuth(unittest.TestCase):
|
|||||||
app = FakeApp()
|
app = FakeApp()
|
||||||
ath = auth.filter_factory({})(app)
|
ath = auth.filter_factory({})(app)
|
||||||
self.assertEquals(ath.reseller_prefix, 'AUTH_')
|
self.assertEquals(ath.reseller_prefix, 'AUTH_')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, ['AUTH_'])
|
||||||
ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app)
|
ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app)
|
||||||
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, ['TEST_'])
|
||||||
ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app)
|
ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app)
|
||||||
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, ['TEST_'])
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': ''})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, '')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, [''])
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': ' '})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, '')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, [''])
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': ' '' '})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, '')
|
||||||
|
self.assertEquals(ath.reseller_prefixes, [''])
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': " '', TEST"})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, '')
|
||||||
|
self.assertTrue('' in ath.reseller_prefixes)
|
||||||
|
self.assertTrue('TEST_' in ath.reseller_prefixes)
|
||||||
|
|
||||||
def test_auth_prefix_init(self):
|
def test_auth_prefix_init(self):
|
||||||
app = FakeApp()
|
app = FakeApp()
|
||||||
@ -264,8 +280,8 @@ class TestAuth(unittest.TestCase):
|
|||||||
req = self._make_request('/v1/account', environ={'swift.authorize':
|
req = self._make_request('/v1/account', environ={'swift.authorize':
|
||||||
local_authorize})
|
local_authorize})
|
||||||
resp = req.get_response(local_auth)
|
resp = req.get_response(local_auth)
|
||||||
self.assertEquals(resp.status_int, 200)
|
|
||||||
self.assertEquals(req.environ['swift.authorize'], local_authorize)
|
self.assertEquals(req.environ['swift.authorize'], local_authorize)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
|
||||||
def test_auth_fail(self):
|
def test_auth_fail(self):
|
||||||
resp = self._make_request(
|
resp = self._make_request(
|
||||||
@ -791,6 +807,7 @@ class TestAuth(unittest.TestCase):
|
|||||||
self.assertEquals(resp, None)
|
self.assertEquals(resp, None)
|
||||||
|
|
||||||
def test_get_user_group(self):
|
def test_get_user_group(self):
|
||||||
|
# More tests in TestGetUserGroups class
|
||||||
app = FakeApp()
|
app = FakeApp()
|
||||||
ath = auth.filter_factory({})(app)
|
ath = auth.filter_factory({})(app)
|
||||||
|
|
||||||
@ -812,6 +829,116 @@ class TestAuth(unittest.TestCase):
|
|||||||
'Swift realm="BLAH_account"')
|
'Swift realm="BLAH_account"')
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthWithMultiplePrefixes(TestAuth):
|
||||||
|
"""
|
||||||
|
Repeats all tests in TestAuth except adds multiple
|
||||||
|
reseller_prefix items
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_auth = auth.filter_factory(
|
||||||
|
{'reseller_prefix': 'AUTH_, SOMEOTHER_, YETANOTHER_'})(FakeApp())
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetUserGroups(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_custom_url_config(self):
|
||||||
|
app = FakeApp()
|
||||||
|
ath = auth.filter_factory({
|
||||||
|
'user_test_tester':
|
||||||
|
'testing .admin http://saio:8080/v1/AUTH_monkey'})(app)
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'AUTH_monkey')
|
||||||
|
self.assertEquals(groups, 'test,test:tester,AUTH_test,AUTH_monkey')
|
||||||
|
|
||||||
|
def test_no_prefix_reseller(self):
|
||||||
|
app = FakeApp()
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': ''})(app)
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': ['.admin']}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'test')
|
||||||
|
self.assertEquals(groups, 'test,test:tester')
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': []}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'test')
|
||||||
|
self.assertEquals(groups, 'test,test:tester')
|
||||||
|
|
||||||
|
def test_single_reseller(self):
|
||||||
|
app = FakeApp()
|
||||||
|
ath = auth.filter_factory({})(app)
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': ['.admin']}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'AUTH_test')
|
||||||
|
self.assertEquals(groups, 'test,test:tester,AUTH_test')
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': []}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'AUTH_test')
|
||||||
|
self.assertEquals(groups, 'test,test:tester')
|
||||||
|
|
||||||
|
def test_multiple_reseller(self):
|
||||||
|
app = FakeApp()
|
||||||
|
ath = auth.filter_factory(
|
||||||
|
{'reseller_prefix': 'AUTH_, SOMEOTHER_, YETANOTHER_'})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefixes, ['AUTH_', 'SOMEOTHER_',
|
||||||
|
'YETANOTHER_'])
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': ['.admin']}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'AUTH_test')
|
||||||
|
self.assertEquals(groups,
|
||||||
|
'test,test:tester,AUTH_test,'
|
||||||
|
'SOMEOTHER_test,YETANOTHER_test')
|
||||||
|
|
||||||
|
ath.users = {'test:tester': {'groups': []}}
|
||||||
|
groups = ath._get_user_groups('test', 'test:tester', 'AUTH_test')
|
||||||
|
self.assertEquals(groups, 'test,test:tester')
|
||||||
|
|
||||||
|
|
||||||
|
class TestDefinitiveAuth(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.test_auth = auth.filter_factory(
|
||||||
|
{'reseller_prefix': 'AUTH_, SOMEOTHER_'})(FakeApp())
|
||||||
|
|
||||||
|
def test_noreseller_prefix(self):
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': ''})(FakeApp())
|
||||||
|
result = ath._is_definitive_auth(path='/v1/test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/AUTH_test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/BLAH_test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
|
||||||
|
def test_blank_prefix(self):
|
||||||
|
ath = auth.filter_factory({'reseller_prefix':
|
||||||
|
" '', SOMEOTHER"})(FakeApp())
|
||||||
|
result = ath._is_definitive_auth(path='/v1/test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/SOMEOTHER_test')
|
||||||
|
self.assertEquals(result, True)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/SOMEOTHERtest')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
|
||||||
|
def test_default_prefix(self):
|
||||||
|
ath = auth.filter_factory({})(FakeApp())
|
||||||
|
result = ath._is_definitive_auth(path='/v1/AUTH_test')
|
||||||
|
self.assertEquals(result, True)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/BLAH_test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': 'AUTH'})(FakeApp())
|
||||||
|
result = ath._is_definitive_auth(path='/v1/AUTH_test')
|
||||||
|
self.assertEquals(result, True)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/BLAH_test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
|
||||||
|
def test_multiple_prefixes(self):
|
||||||
|
ath = auth.filter_factory({'reseller_prefix':
|
||||||
|
'AUTH, SOMEOTHER'})(FakeApp())
|
||||||
|
result = ath._is_definitive_auth(path='/v1/AUTH_test')
|
||||||
|
self.assertEquals(result, True)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/SOMEOTHER_test')
|
||||||
|
self.assertEquals(result, True)
|
||||||
|
result = ath._is_definitive_auth(path='/v1/BLAH_test')
|
||||||
|
self.assertEquals(result, False)
|
||||||
|
|
||||||
|
|
||||||
class TestParseUserCreation(unittest.TestCase):
|
class TestParseUserCreation(unittest.TestCase):
|
||||||
def test_parse_user_creation(self):
|
def test_parse_user_creation(self):
|
||||||
auth_filter = auth.filter_factory({
|
auth_filter = auth.filter_factory({
|
||||||
@ -869,6 +996,15 @@ class TestParseUserCreation(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestAccountAcls(unittest.TestCase):
|
class TestAccountAcls(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
These tests use a single reseller prefix (AUTH_) and the
|
||||||
|
target paths are /v1/AUTH_<blah>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.reseller_prefix = {}
|
||||||
|
self.accpre = 'AUTH'
|
||||||
|
|
||||||
def _make_request(self, path, **kwargs):
|
def _make_request(self, path, **kwargs):
|
||||||
# Our TestAccountAcls default request will have a valid auth token
|
# Our TestAccountAcls default request will have a valid auth token
|
||||||
version, acct, _ = split_path(path, 1, 3, True)
|
version, acct, _ = split_path(path, 1, 3, True)
|
||||||
@ -897,38 +1033,51 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
def _conf(self, moreconf):
|
||||||
|
conf = self.reseller_prefix
|
||||||
|
conf.update(moreconf)
|
||||||
|
return conf
|
||||||
|
|
||||||
def test_account_acl_success(self):
|
def test_account_acl_success(self):
|
||||||
test_auth = auth.filter_factory({'user_admin_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 1)))
|
self._conf({'user_admin_user': 'testing'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 1)))
|
||||||
|
|
||||||
# admin (not a swift admin) wants to read from otheracct
|
# admin (not a swift admin) wants to read from otheracct
|
||||||
req = self._make_request('/v1/AUTH_otheract', user_groups="AUTH_admin")
|
req = self._make_request('/v1/%s_otheract' % self.accpre,
|
||||||
|
user_groups="AUTH_admin")
|
||||||
|
|
||||||
# The request returned by _make_request should be allowed
|
# The request returned by _make_request should be allowed
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 204)
|
self.assertEquals(resp.status_int, 204)
|
||||||
|
|
||||||
def test_account_acl_failures(self):
|
def test_account_acl_failures(self):
|
||||||
test_auth = auth.filter_factory({'user_admin_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp())
|
self._conf({'user_admin_user': 'testing'}))(
|
||||||
|
FakeApp())
|
||||||
|
|
||||||
# If I'm not authed as anyone on the ACLs, I shouldn't get in
|
# If I'm not authed as anyone on the ACLs, I shouldn't get in
|
||||||
req = self._make_request('/v1/AUTH_otheract', user_groups="AUTH_bob")
|
req = self._make_request('/v1/%s_otheract' % self.accpre,
|
||||||
|
user_groups="AUTH_bob")
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 403)
|
self.assertEquals(resp.status_int, 403)
|
||||||
|
|
||||||
# If the target account has no ACLs, a non-owner shouldn't get in
|
# If the target account has no ACLs, a non-owner shouldn't get in
|
||||||
req = self._make_request('/v1/AUTH_otheract', user_groups="AUTH_admin",
|
req = self._make_request('/v1/%s_otheract' % self.accpre,
|
||||||
|
user_groups="AUTH_admin",
|
||||||
acls={})
|
acls={})
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 403)
|
self.assertEquals(resp.status_int, 403)
|
||||||
|
|
||||||
def test_admin_privileges(self):
|
def test_admin_privileges(self):
|
||||||
test_auth = auth.filter_factory({'user_admin_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 18)))
|
self._conf({'user_admin_user': 'testing'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 18)))
|
||||||
|
|
||||||
for target in ('/v1/AUTH_otheracct', '/v1/AUTH_otheracct/container',
|
for target in (
|
||||||
'/v1/AUTH_otheracct/container/obj'):
|
'/v1/%s_otheracct' % self.accpre,
|
||||||
|
'/v1/%s_otheracct/container' % self.accpre,
|
||||||
|
'/v1/%s_otheracct/container/obj' % self.accpre):
|
||||||
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
||||||
# Admin ACL user can do anything
|
# Admin ACL user can do anything
|
||||||
req = self._make_request(target, user_groups="AUTH_admin",
|
req = self._make_request(target, user_groups="AUTH_admin",
|
||||||
@ -941,10 +1090,11 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
self.assertTrue(req.environ.get('swift_owner'))
|
self.assertTrue(req.environ.get('swift_owner'))
|
||||||
|
|
||||||
def test_readwrite_privileges(self):
|
def test_readwrite_privileges(self):
|
||||||
test_auth = auth.filter_factory({'user_rw_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 15)))
|
self._conf({'user_rw_user': 'testing'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 15)))
|
||||||
|
|
||||||
for target in ('/v1/AUTH_otheracct',):
|
for target in ('/v1/%s_otheracct' % self.accpre,):
|
||||||
for method in ('GET', 'HEAD', 'OPTIONS'):
|
for method in ('GET', 'HEAD', 'OPTIONS'):
|
||||||
# Read-Write user can read account data
|
# Read-Write user can read account data
|
||||||
req = self._make_request(target, user_groups="AUTH_rw",
|
req = self._make_request(target, user_groups="AUTH_rw",
|
||||||
@ -964,7 +1114,8 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
|
|
||||||
# RW user should be able to GET, PUT, POST, or DELETE to containers
|
# RW user should be able to GET, PUT, POST, or DELETE to containers
|
||||||
# and objects
|
# and objects
|
||||||
for target in ('/v1/AUTH_otheracct/c', '/v1/AUTH_otheracct/c/o'):
|
for target in ('/v1/%s_otheracct/c' % self.accpre,
|
||||||
|
'/v1/%s_otheracct/c/o' % self.accpre):
|
||||||
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
||||||
req = self._make_request(target, user_groups="AUTH_rw",
|
req = self._make_request(target, user_groups="AUTH_rw",
|
||||||
environ={'REQUEST_METHOD': method})
|
environ={'REQUEST_METHOD': method})
|
||||||
@ -972,13 +1123,15 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 204)
|
self.assertEquals(resp.status_int, 204)
|
||||||
|
|
||||||
def test_readonly_privileges(self):
|
def test_readonly_privileges(self):
|
||||||
test_auth = auth.filter_factory({'user_ro_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 9)))
|
self._conf({'user_ro_user': 'testing'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 9)))
|
||||||
|
|
||||||
# ReadOnly user should NOT be able to PUT, POST, or DELETE to account,
|
# ReadOnly user should NOT be able to PUT, POST, or DELETE to account,
|
||||||
# container, or object
|
# container, or object
|
||||||
for target in ('/v1/AUTH_otheracct', '/v1/AUTH_otheracct/cont',
|
for target in ('/v1/%s_otheracct' % self.accpre,
|
||||||
'/v1/AUTH_otheracct/cont/obj'):
|
'/v1/%s_otheracct/cont' % self.accpre,
|
||||||
|
'/v1/%s_otheracct/cont/obj' % self.accpre):
|
||||||
for method in ('GET', 'HEAD', 'OPTIONS'):
|
for method in ('GET', 'HEAD', 'OPTIONS'):
|
||||||
req = self._make_request(target, user_groups="AUTH_ro",
|
req = self._make_request(target, user_groups="AUTH_ro",
|
||||||
environ={'REQUEST_METHOD': method})
|
environ={'REQUEST_METHOD': method})
|
||||||
@ -995,12 +1148,14 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
self.assertFalse(req.environ.get('swift_owner'))
|
self.assertFalse(req.environ.get('swift_owner'))
|
||||||
|
|
||||||
def test_user_gets_best_acl(self):
|
def test_user_gets_best_acl(self):
|
||||||
test_auth = auth.filter_factory({'user_acct_username': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 18)))
|
self._conf({'user_acct_username': 'testing'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 18)))
|
||||||
|
|
||||||
mygroups = "AUTH_acct,AUTH_ro,AUTH_something,AUTH_admin"
|
mygroups = "AUTH_acct,AUTH_ro,AUTH_something,AUTH_admin"
|
||||||
for target in ('/v1/AUTH_otheracct', '/v1/AUTH_otheracct/container',
|
for target in ('/v1/%s_otheracct' % self.accpre,
|
||||||
'/v1/AUTH_otheracct/container/obj'):
|
'/v1/%s_otheracct/container' % self.accpre,
|
||||||
|
'/v1/%s_otheracct/container/obj' % self.accpre):
|
||||||
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
for method in ('GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'DELETE'):
|
||||||
# Admin ACL user can do anything
|
# Admin ACL user can do anything
|
||||||
req = self._make_request(target, user_groups=mygroups,
|
req = self._make_request(target, user_groups=mygroups,
|
||||||
@ -1015,9 +1170,11 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
self.assertTrue(req.environ.get('swift_owner'))
|
self.assertTrue(req.environ.get('swift_owner'))
|
||||||
|
|
||||||
def test_acl_syntax_verification(self):
|
def test_acl_syntax_verification(self):
|
||||||
test_auth = auth.filter_factory({'user_admin_user': 'testing'})(
|
test_auth = auth.filter_factory(
|
||||||
FakeApp(iter(NO_CONTENT_RESP * 5)))
|
self._conf({'user_admin_user': 'testing .admin'}))(
|
||||||
|
FakeApp(iter(NO_CONTENT_RESP * 5)))
|
||||||
|
user_groups = test_auth._get_user_groups('admin', 'admin:user',
|
||||||
|
'AUTH_admin')
|
||||||
good_headers = {'X-Auth-Token': 'AUTH_t'}
|
good_headers = {'X-Auth-Token': 'AUTH_t'}
|
||||||
good_acl = '{"read-only":["a","b"]}'
|
good_acl = '{"read-only":["a","b"]}'
|
||||||
bad_acl = 'syntactically invalid acl -- this does not parse as JSON'
|
bad_acl = 'syntactically invalid acl -- this does not parse as JSON'
|
||||||
@ -1026,23 +1183,25 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
not_dict_acl = '["read-only"]'
|
not_dict_acl = '["read-only"]'
|
||||||
not_dict_acl2 = 1
|
not_dict_acl2 = 1
|
||||||
empty_acls = ['{}', '', '{ }']
|
empty_acls = ['{}', '', '{ }']
|
||||||
target = '/v1/AUTH_firstacct'
|
target = '/v1/%s_firstacct' % self.accpre
|
||||||
|
|
||||||
# no acls -- no problem!
|
# no acls -- no problem!
|
||||||
req = self._make_request(target, headers=good_headers)
|
req = self._make_request(target, headers=good_headers,
|
||||||
|
user_groups=user_groups)
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 204)
|
self.assertEquals(resp.status_int, 204)
|
||||||
|
|
||||||
# syntactically valid acls should go through
|
# syntactically valid acls should go through
|
||||||
update = {'x-account-access-control': good_acl}
|
update = {'x-account-access-control': good_acl}
|
||||||
req = self._make_request(target, headers=dict(good_headers, **update))
|
req = self._make_request(target, user_groups=user_groups,
|
||||||
|
headers=dict(good_headers, **update))
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 204)
|
self.assertEquals(resp.status_int, 204)
|
||||||
|
|
||||||
# syntactically valid empty acls should go through
|
# syntactically valid empty acls should go through
|
||||||
for acl in empty_acls:
|
for acl in empty_acls:
|
||||||
update = {'x-account-access-control': acl}
|
update = {'x-account-access-control': acl}
|
||||||
req = self._make_request(target,
|
req = self._make_request(target, user_groups=user_groups,
|
||||||
headers=dict(good_headers, **update))
|
headers=dict(good_headers, **update))
|
||||||
resp = req.get_response(test_auth)
|
resp = req.get_response(test_auth)
|
||||||
self.assertEquals(resp.status_int, 204)
|
self.assertEquals(resp.status_int, 204)
|
||||||
@ -1125,6 +1284,299 @@ class TestAccountAcls(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 400)
|
self.assertEquals(resp.status_int, 400)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthMultiplePrefixes(TestAccountAcls):
|
||||||
|
"""
|
||||||
|
These tests repeat the same tests as TestAccountACLs,
|
||||||
|
but use multiple reseller prefix items (AUTH_ and SOMEOTHER_).
|
||||||
|
The target paths are /v1/SOMEOTHER_<blah>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.reseller_prefix = {'reseller_prefix': 'AUTH_, SOMEOTHER_'}
|
||||||
|
self.accpre = 'SOMEOTHER'
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixAccount(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_default(self):
|
||||||
|
conf = {}
|
||||||
|
test_auth = auth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
|
||||||
|
def test_same_as_default(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH'}
|
||||||
|
test_auth = auth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
|
||||||
|
def test_blank_reseller(self):
|
||||||
|
conf = {'reseller_prefix': ''}
|
||||||
|
test_auth = auth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'1234'), '')
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), '') # yes, it should return ''
|
||||||
|
|
||||||
|
def test_multiple_resellers(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2'}
|
||||||
|
test_auth = auth.filter_factory(conf)(FakeApp())
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'AUTH_1234'), 'AUTH_')
|
||||||
|
self.assertEquals(test_auth._get_account_prefix(
|
||||||
|
'JUNK_1234'), None)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceTokenFunctionality(unittest.TestCase):
|
||||||
|
|
||||||
|
def _make_authed_request(self, conf, remote_user, path, method='GET'):
|
||||||
|
"""Make a request with tempauth as auth
|
||||||
|
|
||||||
|
Acts as though the user had presented a token
|
||||||
|
granting groups as described in remote_user.
|
||||||
|
If remote_user contains the .service group, it emulates presenting
|
||||||
|
X-Service-Token containing a .service group.
|
||||||
|
|
||||||
|
:param conf: configuration for tempauth
|
||||||
|
:param remote_user: the groups the user belongs to. Examples:
|
||||||
|
acct:joe,acct user joe, no .admin
|
||||||
|
acct:joe,acct,AUTH_joeacct user joe, jas .admin group
|
||||||
|
acct:joe,acct,AUTH_joeacct,.service adds .service group
|
||||||
|
:param path: the path of the request
|
||||||
|
:param method: the method (defaults to GET)
|
||||||
|
|
||||||
|
:returns: response object
|
||||||
|
"""
|
||||||
|
self.req = Request.blank(path)
|
||||||
|
self.req.method = method
|
||||||
|
self.req.remote_user = remote_user
|
||||||
|
fake_app = FakeApp(iter([('200 OK', {}, '')]))
|
||||||
|
test_auth = auth.filter_factory(conf)(fake_app)
|
||||||
|
resp = self.req.get_response(test_auth)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def test_authed_for_path_single(self):
|
||||||
|
resp = self._make_authed_request({}, 'acct:joe,acct,AUTH_acct',
|
||||||
|
'/v1/AUTH_acct')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'}, 'acct:joe,acct,AUTH_acct',
|
||||||
|
'/v1/AUTH_acct/c', method='PUT')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'},
|
||||||
|
'admin:mary,admin,AUTH_admin,.reseller_admin',
|
||||||
|
'/v1/AUTH_acct', method='GET')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'},
|
||||||
|
'admin:mary,admin,AUTH_admin,.reseller_admin',
|
||||||
|
'/v1/AUTH_acct', method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_path_single(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'},
|
||||||
|
'fredacc:fred,fredacct,AUTH_fredacc',
|
||||||
|
'/v1/AUTH_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'},
|
||||||
|
'acct:joe,acct',
|
||||||
|
'/v1/AUTH_acct',
|
||||||
|
method='PUT')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH'},
|
||||||
|
'acct:joe,acct,AUTH_acct',
|
||||||
|
'/v1/AUTH_acct',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_authed_for_primary_path_multiple(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_with_only_operator_role(self):
|
||||||
|
# User only presents a token in X-Auth-Token (or in X-Service-Token)
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
# User puts token in both X-Auth-Token and X-Service-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct,AUTH_acct,PRE2_acct',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_authed_for_second_path_with_operator_role_and_service(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct,'
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_with_only_service(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_denied_for_second_path_for_service_user(self):
|
||||||
|
# User presents token with 'service' role in X-Auth-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
# User presents token with 'service' role in X-Auth-Token
|
||||||
|
# and also in X-Service-Token
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service,'
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service',
|
||||||
|
'/v1/PRE2_acct')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_delete_denied_for_second_path(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct,'
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.service',
|
||||||
|
'/v1/PRE2_acct',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_delete_of_second_path_by_reseller_admin(self):
|
||||||
|
resp = self._make_authed_request(
|
||||||
|
{'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'},
|
||||||
|
'acct:joe,acct,AUTH_acct,PRE2_acct,'
|
||||||
|
'admin:mary,admin,AUTH_admin,PRE2_admin,.reseller_admin',
|
||||||
|
'/v1/PRE2_acct',
|
||||||
|
method='DELETE')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTokenHandling(unittest.TestCase):
|
||||||
|
|
||||||
|
def _make_request(self, conf, path, headers, method='GET'):
|
||||||
|
"""Make a request with tempauth as auth
|
||||||
|
|
||||||
|
It sets up AUTH_t and AUTH_s as tokens in memcache, where "joe"
|
||||||
|
has .admin role on /v1/AUTH_acct and user "glance" has .service
|
||||||
|
role on /v1/AUTH_admin.
|
||||||
|
|
||||||
|
:param conf: configuration for tempauth
|
||||||
|
:param path: the path of the request
|
||||||
|
:param headers: allows you to pass X-Auth-Token, etc.
|
||||||
|
:param method: the method (defaults to GET)
|
||||||
|
|
||||||
|
:returns: response object
|
||||||
|
"""
|
||||||
|
fake_app = FakeApp(iter([('200 OK', {}, '')]))
|
||||||
|
self.test_auth = auth.filter_factory(conf)(fake_app)
|
||||||
|
self.req = Request.blank(path, headers=headers)
|
||||||
|
self.req.method = method
|
||||||
|
self.req.environ['swift.cache'] = FakeMemcache()
|
||||||
|
self._setup_user_and_token('AUTH_t', 'acct', 'acct:joe',
|
||||||
|
'.admin')
|
||||||
|
self._setup_user_and_token('AUTH_s', 'admin', 'admin:glance',
|
||||||
|
'.service')
|
||||||
|
resp = self.req.get_response(self.test_auth)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def _setup_user_and_token(self, token_name, account, account_user,
|
||||||
|
groups):
|
||||||
|
"""Setup named token in memcache
|
||||||
|
|
||||||
|
:param token_name: name of token
|
||||||
|
:param account: example: acct
|
||||||
|
:param account_user: example: acct_joe
|
||||||
|
:param groups: example: .admin
|
||||||
|
"""
|
||||||
|
self.test_auth.users[account_user] = dict(groups=[groups])
|
||||||
|
account_id = 'AUTH_%s' % account
|
||||||
|
cache_key = 'AUTH_/token/%s' % token_name
|
||||||
|
cache_entry = (time() + 3600,
|
||||||
|
self.test_auth._get_user_groups(account,
|
||||||
|
account_user,
|
||||||
|
account_id))
|
||||||
|
self.req.environ['swift.cache'].set(cache_key, cache_entry)
|
||||||
|
|
||||||
|
def test_tokens_set_remote_user(self):
|
||||||
|
conf = {} # Default conf
|
||||||
|
resp = self._make_request(conf, '/v1/AUTH_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t'})
|
||||||
|
self.assertEqual(self.req.environ['REMOTE_USER'],
|
||||||
|
'acct,acct:joe,AUTH_acct')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
# Add x-service-token
|
||||||
|
resp = self._make_request(conf, '/v1/AUTH_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t',
|
||||||
|
'x-service-token': 'AUTH_s'})
|
||||||
|
self.assertEqual(self.req.environ['REMOTE_USER'],
|
||||||
|
'acct,acct:joe,AUTH_acct,admin,admin:glance,.service')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
# Put x-auth-token value into x-service-token
|
||||||
|
resp = self._make_request(conf, '/v1/AUTH_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t',
|
||||||
|
'x-service-token': 'AUTH_t'})
|
||||||
|
self.assertEqual(self.req.environ['REMOTE_USER'],
|
||||||
|
'acct,acct:joe,AUTH_acct,acct,acct:joe,AUTH_acct')
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_service_token_given_and_needed(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'}
|
||||||
|
resp = self._make_request(conf, '/v1/PRE2_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t',
|
||||||
|
'x-service-token': 'AUTH_s'})
|
||||||
|
self.assertEqual(resp.status_int, 200)
|
||||||
|
|
||||||
|
def test_service_token_omitted(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'}
|
||||||
|
resp = self._make_request(conf, '/v1/PRE2_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t'})
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
|
||||||
|
def test_invalid_tokens(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2',
|
||||||
|
'PRE2_require_group': '.service'}
|
||||||
|
resp = self._make_request(conf, '/v1/PRE2_acct',
|
||||||
|
{'x-auth-token': 'AUTH_junk'})
|
||||||
|
self.assertEqual(resp.status_int, 401)
|
||||||
|
resp = self._make_request(conf, '/v1/PRE2_acct',
|
||||||
|
{'x-auth-token': 'AUTH_t',
|
||||||
|
'x-service-token': 'AUTH_junk'})
|
||||||
|
self.assertEqual(resp.status_int, 403)
|
||||||
|
resp = self._make_request(conf, '/v1/PRE2_acct',
|
||||||
|
{'x-auth-token': 'AUTH_junk',
|
||||||
|
'x-service-token': 'AUTH_s'})
|
||||||
|
self.assertEqual(resp.status_int, 401)
|
||||||
|
|
||||||
|
|
||||||
class TestUtilityMethods(unittest.TestCase):
|
class TestUtilityMethods(unittest.TestCase):
|
||||||
def test_account_acls_bad_path_raises_exception(self):
|
def test_account_acls_bad_path_raises_exception(self):
|
||||||
auth_inst = auth.filter_factory({})(FakeApp())
|
auth_inst = auth.filter_factory({})(FakeApp())
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
# Copyright (c) 2013 Alex Gaynor
|
# Copyright (c) 2013 Alex Gaynor
|
||||||
# Copyright (c) 2013 Chuck Thier
|
# Copyright (c) 2013 Chuck Thier
|
||||||
# Copyright (c) 2013 David Goetz
|
# Copyright (c) 2013 David Goetz
|
||||||
# Copyright (c) 2013 Donagh McCabe
|
# Copyright (c) 2015 Donagh McCabe
|
||||||
# Copyright (c) 2013 Greg Lange
|
# Copyright (c) 2013 Greg Lange
|
||||||
# Copyright (c) 2013 John Dickinson
|
# Copyright (c) 2013 John Dickinson
|
||||||
# Copyright (c) 2013 Kun Huang
|
# Copyright (c) 2013 Kun Huang
|
||||||
@ -66,8 +66,7 @@ class TestTempURL(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = FakeApp()
|
self.app = FakeApp()
|
||||||
self.auth = tempauth.filter_factory({})(self.app)
|
self.auth = tempauth.filter_factory({'reseller_prefix': ''})(self.app)
|
||||||
self.auth.reseller_prefix = 'a'
|
|
||||||
self.tempurl = tempurl.filter_factory({})(self.auth)
|
self.tempurl = tempurl.filter_factory({})(self.auth)
|
||||||
|
|
||||||
def _make_request(self, path, environ=None, keys=(), **kwargs):
|
def _make_request(self, path, environ=None, keys=(), **kwargs):
|
||||||
|
@ -2806,6 +2806,186 @@ cluster_dfw1 = http://dfw1.host/v1/
|
|||||||
self.assertEqual(0, len(logger.get_lines_for_level('error')))
|
self.assertEqual(0, len(logger.get_lines_for_level('error')))
|
||||||
|
|
||||||
|
|
||||||
|
class ResellerConfReader(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.default_rules = {'operator_roles': ['admin', 'swiftoperator'],
|
||||||
|
'service_roles': [],
|
||||||
|
'require_group': ''}
|
||||||
|
|
||||||
|
def test_defaults(self):
|
||||||
|
conf = {}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_'])
|
||||||
|
self.assertEqual(options['AUTH_'], self.default_rules)
|
||||||
|
|
||||||
|
def test_same_as_default(self):
|
||||||
|
conf = {'reseller_prefix': 'AUTH',
|
||||||
|
'operator_roles': 'admin, swiftoperator'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_'])
|
||||||
|
self.assertEqual(options['AUTH_'], self.default_rules)
|
||||||
|
|
||||||
|
def test_single_blank_reseller(self):
|
||||||
|
conf = {'reseller_prefix': ''}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, [''])
|
||||||
|
self.assertEqual(options[''], self.default_rules)
|
||||||
|
|
||||||
|
def test_single_blank_reseller_with_conf(self):
|
||||||
|
conf = {'reseller_prefix': '',
|
||||||
|
"''operator_roles": 'role1, role2'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, [''])
|
||||||
|
self.assertEqual(options[''].get('operator_roles'),
|
||||||
|
['role1', 'role2'])
|
||||||
|
self.assertEqual(options[''].get('service_roles'),
|
||||||
|
self.default_rules.get('service_roles'))
|
||||||
|
self.assertEqual(options[''].get('require_group'),
|
||||||
|
self.default_rules.get('require_group'))
|
||||||
|
|
||||||
|
def test_multiple_same_resellers(self):
|
||||||
|
conf = {'reseller_prefix': " '' , '' "}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, [''])
|
||||||
|
|
||||||
|
conf = {'reseller_prefix': '_, _'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['_'])
|
||||||
|
|
||||||
|
conf = {'reseller_prefix': 'AUTH, PRE2, AUTH, PRE2'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_', 'PRE2_'])
|
||||||
|
|
||||||
|
def test_several_resellers_with_conf(self):
|
||||||
|
conf = {'reseller_prefix': 'PRE1, PRE2',
|
||||||
|
'PRE1_operator_roles': 'role1, role2',
|
||||||
|
'PRE1_service_roles': 'role3, role4',
|
||||||
|
'PRE2_operator_roles': 'role5',
|
||||||
|
'PRE2_service_roles': 'role6',
|
||||||
|
'PRE2_require_group': 'pre2_group'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['PRE1_', 'PRE2_'])
|
||||||
|
|
||||||
|
self.assertEquals(set(['role1', 'role2']),
|
||||||
|
set(options['PRE1_'].get('operator_roles')))
|
||||||
|
self.assertEquals(['role5'],
|
||||||
|
options['PRE2_'].get('operator_roles'))
|
||||||
|
self.assertEquals(set(['role3', 'role4']),
|
||||||
|
set(options['PRE1_'].get('service_roles')))
|
||||||
|
self.assertEquals(['role6'], options['PRE2_'].get('service_roles'))
|
||||||
|
self.assertEquals('', options['PRE1_'].get('require_group'))
|
||||||
|
self.assertEquals('pre2_group', options['PRE2_'].get('require_group'))
|
||||||
|
|
||||||
|
def test_several_resellers_first_blank(self):
|
||||||
|
conf = {'reseller_prefix': " '' , PRE2",
|
||||||
|
"''operator_roles": 'role1, role2',
|
||||||
|
"''service_roles": 'role3, role4',
|
||||||
|
'PRE2_operator_roles': 'role5',
|
||||||
|
'PRE2_service_roles': 'role6',
|
||||||
|
'PRE2_require_group': 'pre2_group'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['', 'PRE2_'])
|
||||||
|
|
||||||
|
self.assertEquals(set(['role1', 'role2']),
|
||||||
|
set(options[''].get('operator_roles')))
|
||||||
|
self.assertEquals(['role5'],
|
||||||
|
options['PRE2_'].get('operator_roles'))
|
||||||
|
self.assertEquals(set(['role3', 'role4']),
|
||||||
|
set(options[''].get('service_roles')))
|
||||||
|
self.assertEquals(['role6'], options['PRE2_'].get('service_roles'))
|
||||||
|
self.assertEquals('', options[''].get('require_group'))
|
||||||
|
self.assertEquals('pre2_group', options['PRE2_'].get('require_group'))
|
||||||
|
|
||||||
|
def test_several_resellers_with_blank_comma(self):
|
||||||
|
conf = {'reseller_prefix': "AUTH , '', PRE2",
|
||||||
|
"''operator_roles": 'role1, role2',
|
||||||
|
"''service_roles": 'role3, role4',
|
||||||
|
'PRE2_operator_roles': 'role5',
|
||||||
|
'PRE2_service_roles': 'role6',
|
||||||
|
'PRE2_require_group': 'pre2_group'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_', '', 'PRE2_'])
|
||||||
|
self.assertEquals(set(['admin', 'swiftoperator']),
|
||||||
|
set(options['AUTH_'].get('operator_roles')))
|
||||||
|
self.assertEquals(set(['role1', 'role2']),
|
||||||
|
set(options[''].get('operator_roles')))
|
||||||
|
self.assertEquals(['role5'],
|
||||||
|
options['PRE2_'].get('operator_roles'))
|
||||||
|
self.assertEquals([],
|
||||||
|
options['AUTH_'].get('service_roles'))
|
||||||
|
self.assertEquals(set(['role3', 'role4']),
|
||||||
|
set(options[''].get('service_roles')))
|
||||||
|
self.assertEquals(['role6'], options['PRE2_'].get('service_roles'))
|
||||||
|
self.assertEquals('', options['AUTH_'].get('require_group'))
|
||||||
|
self.assertEquals('', options[''].get('require_group'))
|
||||||
|
self.assertEquals('pre2_group', options['PRE2_'].get('require_group'))
|
||||||
|
|
||||||
|
def test_stray_comma(self):
|
||||||
|
conf = {'reseller_prefix': "AUTH ,, PRE2",
|
||||||
|
"''operator_roles": 'role1, role2',
|
||||||
|
"''service_roles": 'role3, role4',
|
||||||
|
'PRE2_operator_roles': 'role5',
|
||||||
|
'PRE2_service_roles': 'role6',
|
||||||
|
'PRE2_require_group': 'pre2_group'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_', 'PRE2_'])
|
||||||
|
self.assertEquals(set(['admin', 'swiftoperator']),
|
||||||
|
set(options['AUTH_'].get('operator_roles')))
|
||||||
|
self.assertEquals(['role5'],
|
||||||
|
options['PRE2_'].get('operator_roles'))
|
||||||
|
self.assertEquals([],
|
||||||
|
options['AUTH_'].get('service_roles'))
|
||||||
|
self.assertEquals(['role6'], options['PRE2_'].get('service_roles'))
|
||||||
|
self.assertEquals('', options['AUTH_'].get('require_group'))
|
||||||
|
self.assertEquals('pre2_group', options['PRE2_'].get('require_group'))
|
||||||
|
|
||||||
|
def test_multiple_stray_commas_resellers(self):
|
||||||
|
conf = {'reseller_prefix': ' , , ,'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, [''])
|
||||||
|
self.assertEqual(options[''], self.default_rules)
|
||||||
|
|
||||||
|
def test_unprefixed_options(self):
|
||||||
|
conf = {'reseller_prefix': "AUTH , '', PRE2",
|
||||||
|
"operator_roles": 'role1, role2',
|
||||||
|
"service_roles": 'role3, role4',
|
||||||
|
'require_group': 'auth_blank_group',
|
||||||
|
'PRE2_operator_roles': 'role5',
|
||||||
|
'PRE2_service_roles': 'role6',
|
||||||
|
'PRE2_require_group': 'pre2_group'}
|
||||||
|
prefixes, options = utils.config_read_reseller_options(
|
||||||
|
conf, self.default_rules)
|
||||||
|
self.assertEqual(prefixes, ['AUTH_', '', 'PRE2_'])
|
||||||
|
self.assertEquals(set(['role1', 'role2']),
|
||||||
|
set(options['AUTH_'].get('operator_roles')))
|
||||||
|
self.assertEquals(set(['role1', 'role2']),
|
||||||
|
set(options[''].get('operator_roles')))
|
||||||
|
self.assertEquals(['role5'],
|
||||||
|
options['PRE2_'].get('operator_roles'))
|
||||||
|
self.assertEquals(set(['role3', 'role4']),
|
||||||
|
set(options['AUTH_'].get('service_roles')))
|
||||||
|
self.assertEquals(set(['role3', 'role4']),
|
||||||
|
set(options[''].get('service_roles')))
|
||||||
|
self.assertEquals(['role6'], options['PRE2_'].get('service_roles'))
|
||||||
|
self.assertEquals('auth_blank_group',
|
||||||
|
options['AUTH_'].get('require_group'))
|
||||||
|
self.assertEquals('auth_blank_group', options[''].get('require_group'))
|
||||||
|
self.assertEquals('pre2_group', options['PRE2_'].get('require_group'))
|
||||||
|
|
||||||
|
|
||||||
class TestSwiftInfo(unittest.TestCase):
|
class TestSwiftInfo(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user