Skip to content

Commit 7d1285d

Browse files
authored
Adds authn/root API endpoint (#1331)
Issues: Fixes #1330 Problem: The authn/root API was not available. This API is really weird because its more like a fire-and-forget API. It provides no kind or selfLink in its return value. Analysis: Had to work around the resource deficiencies. Introduced our own kind and selfLink values because BIG-IP does not provide them. All methods except create are invalid. Tests: functional unit
1 parent 4daeb23 commit 7d1285d

4 files changed

Lines changed: 237 additions & 0 deletions

File tree

f5/bigip/shared/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#
1717

1818
from f5.bigip.resource import OrganizingCollection
19+
from f5.bigip.shared.authn import Authn
1920
from f5.bigip.shared.authz import Authz
2021
from f5.bigip.shared.file_transfer import File_Transfer
2122
from f5.bigip.shared.iapp import Iapp
@@ -28,5 +29,6 @@ def __init__(self, mgmt):
2829
self._meta_data['allowed_lazy_attributes'] = [
2930
File_Transfer,
3031
Iapp,
32+
Authn,
3133
Authz
3234
]

f5/bigip/shared/authn.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# coding=utf-8
2+
#
3+
# Copyright 2017 F5 Networks Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
19+
from f5.bigip.resource import Collection
20+
from f5.bigip.resource import OrganizingCollection
21+
from f5.bigip.resource import Resource
22+
from f5.sdk_exception import UnsupportedMethod
23+
from f5.sdk_exception import UnsupportedOperation
24+
from f5.sdk_exception import URICreationCollision
25+
26+
27+
class Authn(OrganizingCollection):
28+
def __init__(self, shared):
29+
super(Authn, self).__init__(shared)
30+
self._meta_data['allowed_lazy_attributes'] = [
31+
Roots
32+
]
33+
34+
35+
class Roots(Collection):
36+
def __init__(self, authn):
37+
super(Roots, self).__init__(authn)
38+
self._meta_data['allowed_lazy_attributes'] = [Root]
39+
40+
def get_collection(self, **kwargs):
41+
raise UnsupportedMethod(
42+
"%s does not support get_collection" % self.__class__.__name__
43+
)
44+
45+
46+
class Root(Resource):
47+
def __init__(self, roots):
48+
super(Root, self).__init__(roots)
49+
self._meta_data['required_json_kind'] = 'shared:authn:authrootitemstate'
50+
51+
# The required parameters are a little vague. It turns out that the "user"
52+
# value that is required is the "link" to the ID
53+
self._meta_data['required_creation_parameters'] = {'oldPassword', 'newPassword'}
54+
55+
def _create(self, **kwargs):
56+
"""wrapped by `create` override that in subclasses to customize"""
57+
if 'uri' in self._meta_data:
58+
error = "There was an attempt to assign a new uri to this "\
59+
"resource, the _meta_data['uri'] is %s and it should"\
60+
" not be changed." % (self._meta_data['uri'])
61+
raise URICreationCollision(error)
62+
self._check_exclusive_parameters(**kwargs)
63+
requests_params = self._handle_requests_params(kwargs)
64+
self._minimum_one_is_missing(**kwargs)
65+
self._check_create_parameters(**kwargs)
66+
kwargs = self._check_for_python_keywords(kwargs)
67+
68+
# Reduce boolean pairs as specified by the meta_data entry below
69+
for key1, key2 in self._meta_data['reduction_forcing_pairs']:
70+
kwargs = self._reduce_boolean_pair(kwargs, key1, key2)
71+
72+
# Make convenience variable with short names for this method.
73+
_create_uri = self._meta_data['container']._meta_data['uri']
74+
session = self._meta_data['bigip']._meta_data['icr_session']
75+
76+
kwargs = self._prepare_request_json(kwargs)
77+
78+
# Invoke the REST operation on the device.
79+
response = session.post(_create_uri, json=kwargs, **requests_params)
80+
81+
# Make new instance of self
82+
result = self._produce_instance(response)
83+
return result
84+
85+
def _local_update(self, rdict):
86+
super(Root, self)._local_update(rdict)
87+
88+
# This API returns no kind, so we need to make our own
89+
self.__dict__.update(dict(kind='shared:authn:authrootitemstate'))
90+
91+
# This API returns no selfLink, so we need to make our own
92+
tmos_version = self._meta_data['bigip']._meta_data['tmos_version']
93+
self_link = 'https://localhost/mgmt/shared/authn/root?ver={0}'.format(tmos_version)
94+
self.__dict__.update(dict(selfLink=self_link))
95+
96+
def update(self, **kwargs):
97+
raise UnsupportedOperation(
98+
"%s does not support update" % self.__class__.__name__
99+
)
100+
101+
def load(self, **kwargs):
102+
raise UnsupportedOperation(
103+
"%s does not support load" % self.__class__.__name__
104+
)
105+
106+
def modify(self, **kwargs):
107+
raise UnsupportedOperation(
108+
"%s does not support modify" % self.__class__.__name__
109+
)
110+
111+
def delete(self, **kwargs):
112+
raise UnsupportedOperation(
113+
"%s does not support delete" % self.__class__.__name__
114+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright 2017 F5 Networks Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
17+
import pytest
18+
19+
from distutils.version import LooseVersion
20+
21+
pytestmark = pytest.mark.skipif(
22+
LooseVersion(pytest.config.getoption('--release'))
23+
< LooseVersion('12.0.0'),
24+
reason='Needs v12 TMOS or greater to pass.'
25+
)
26+
27+
28+
@pytest.fixture(scope='function')
29+
def root_credentials(mgmt_root):
30+
result = mgmt_root.shared.authn.roots.root.create(
31+
oldPassword='default',
32+
newPassword='ChangeMyPassword1234'
33+
)
34+
yield result
35+
mgmt_root.shared.authn.roots.root.create(
36+
oldPassword='ChangeMyPassword1234',
37+
newPassword='default'
38+
)
39+
40+
41+
@pytest.mark.skipif(
42+
LooseVersion(pytest.config.getoption('--release')) >= LooseVersion('12.1.0'),
43+
reason='This fixture requires < 12.1.0.'
44+
)
45+
class TestAuthnV12(object):
46+
def test_create(self, root_credentials):
47+
assert root_credentials.newPassword == 'ChangeMyPassword1234'
48+
49+
50+
@pytest.mark.skipif(
51+
LooseVersion(pytest.config.getoption('--release')) < LooseVersion('12.1.0'),
52+
reason='This fixture requires >= 12.1.0.'
53+
)
54+
class TestAuthnPostV12(object):
55+
def test_create(self, root_credentials):
56+
assert root_credentials.generation == 0
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2017 F5 Networks Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
from f5.bigip.shared.authn import Root
17+
from f5.bigip.shared.authn import Roots
18+
from f5.sdk_exception import MissingRequiredCreationParameter
19+
from f5.sdk_exception import UnsupportedMethod
20+
from f5.sdk_exception import UnsupportedOperation
21+
22+
import mock
23+
import pytest
24+
25+
26+
@pytest.fixture
27+
def FakeAuthnRoot():
28+
mo = mock.MagicMock()
29+
fake = Root(mo)
30+
return fake
31+
32+
33+
@pytest.fixture
34+
def FakeAuthnRoots():
35+
mo = mock.MagicMock()
36+
fake = Roots(mo)
37+
return fake
38+
39+
40+
class TestAuthnRoot(object):
41+
def test_update_raises(self, FakeAuthnRoot):
42+
with pytest.raises(UnsupportedOperation):
43+
FakeAuthnRoot.update()
44+
45+
def test_modify_raises(self, FakeAuthnRoot):
46+
with pytest.raises(UnsupportedOperation):
47+
FakeAuthnRoot.modify()
48+
49+
def test_load_raises(self, FakeAuthnRoot):
50+
with pytest.raises(UnsupportedOperation):
51+
FakeAuthnRoot.load()
52+
53+
def test_delete_raises(self, FakeAuthnRoot):
54+
with pytest.raises(UnsupportedOperation):
55+
FakeAuthnRoot.delete()
56+
57+
def test_create_no_args(self, FakeAuthnRoot):
58+
with pytest.raises(MissingRequiredCreationParameter):
59+
FakeAuthnRoot.create()
60+
61+
62+
class TestAuthnRoots(object):
63+
def test_collection_raises(self, FakeAuthnRoots):
64+
with pytest.raises(UnsupportedMethod):
65+
FakeAuthnRoots.get_collection()

0 commit comments

Comments
 (0)