Skip to content

Commit

Permalink
added http api for deleting auth roles (#1205)
Browse files Browse the repository at this point in the history
* added http api for deleting auth roles

* added http api doc update

* additional tests to raise coverage level

* lock user on archive

* added check for archived value being 0 or 1
  • Loading branch information
invisig0th committed Apr 17, 2019
1 parent 38b2b2f commit 8518e46
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 5 deletions.
16 changes: 16 additions & 0 deletions docs/synapse/httpapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,22 @@ session may then be used to call other HTTP API endpoints as the authenticated u
*Returns*
The newly created role dictionary.

/api/v1/auth/delrole
~~~~~~~~~~~~~~~~~~~~

*Method*
POST

This API endpoint allows the caller to delete a role from the system.

*Input*
This API expects the following JSON body::

{ "name": "myrole" }

*Returns*
null

/api/v1/auth/user/<id>
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 2 additions & 0 deletions synapse/lib/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ async def fini():
self.addHttpApi('/api/v1/auth/adduser', s_httpapi.AuthAddUserV1, {'cell': self})
self.addHttpApi('/api/v1/auth/addrole', s_httpapi.AuthAddRoleV1, {'cell': self})

self.addHttpApi('/api/v1/auth/delrole', s_httpapi.AuthDelRoleV1, {'cell': self})

self.addHttpApi('/api/v1/auth/user/(.*)', s_httpapi.AuthUserV1, {'cell': self})
self.addHttpApi('/api/v1/auth/role/(.*)', s_httpapi.AuthRoleV1, {'cell': self})

Expand Down
6 changes: 6 additions & 0 deletions synapse/lib/hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ async def __anit__(self, auth, node):
self.info.setdefault('admin', False)
self.info.setdefault('passwd', None)
self.info.setdefault('locked', False)
self.info.setdefault('archived', False)

self.roles = self.info.get('roles', onedit=self._onRolesEdit)
self.admin = self.info.get('admin', onedit=self._onAdminEdit)
Expand Down Expand Up @@ -940,6 +941,11 @@ async def setAdmin(self, admin):
async def setLocked(self, locked):
await self.info.set('locked', locked)

async def setArchived(self, archived):
await self.info.set('archived', archived)
if archived:
await self.setLocked(True)

def tryPasswd(self, passwd):

if self.locked:
Expand Down
48 changes: 44 additions & 4 deletions synapse/lib/httpapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,21 @@ async def get(self):
if not await self.reqAuthUser():
return

self.sendRestRetn([u.pack() for u in self.cell.auth.users()])
try:

archived = int(self.get_argument('archived', default='0'))
if archived not in (0, 1):
return self.sendRestErr('BadHttpParam', 'The parameter "archived" must be 0 or 1 if specified.')

except Exception as e:
return self.sendRestErr('BadHttpParam', 'The parameter "archived" must be 0 or 1 if specified.')

if archived:
self.sendRestRetn([u.pack() for u in self.cell.auth.users()])
return

self.sendRestRetn([u.pack() for u in self.cell.auth.users() if not u.info.get('archived')])
return

class AuthRolesV1(Handler):

Expand Down Expand Up @@ -340,6 +354,10 @@ async def post(self, iden):
if admin is not None:
await user.setAdmin(bool(admin))

archived = body.get('archived')
if archived is not None:
await user.setArchived(bool(archived))

self.sendRestRetn(user.pack())

class AuthRoleV1(Handler):
Expand Down Expand Up @@ -513,6 +531,31 @@ async def post(self):
self.sendRestRetn(role.pack())
return

class AuthDelRoleV1(Handler):

async def post(self):

if not await self.reqAuthAdmin():
return

body = self.getJsonBody()
if body is None:
return

name = body.get('name')
if name is None:
self.sendRestErr('MissingField', 'The delrole API requires a "name" argument.')
return

role = self.cell.auth.getRoleByName(name)
if role is None:
return self.sendRestErr('NoSuchRole', f'The role {name} does not exist!')

await self.cell.auth.delRole(name)

self.sendRestRetn(None)
return

class ModelNormV1(Handler):

async def get(self):
Expand All @@ -539,8 +582,5 @@ async def get(self):
valu, info = prop.type.norm(propvalu)
self.sendRestRetn({'norm': valu, 'info': info})

except asyncio.CancelledError:
raise

except Exception as e:
return self.sendRestExc(e)
119 changes: 119 additions & 0 deletions synapse/tests/test_lib_httpapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,125 @@

class HttpApiTest(s_tests.SynTest):

async def test_http_user_archived(self):

async with self.getTestCore() as core:

host, port = await core.addHttpsPort(0, host='127.0.0.1')

root = core.auth.getUserByName('root')
await root.setPasswd('secret')

newb = await core.auth.addUser('newb')

async with self.getHttpSess(auth=('root', 'secret'), port=port) as sess:

async with sess.get(f'https://localhost:{port}/api/v1/auth/users') as resp:
item = await resp.json()
users = item.get('result')
self.isin('newb', [u.get('name') for u in users])

info = {'archived': True}
async with sess.post(f'https://localhost:{port}/api/v1/auth/user/{newb.iden}', json=info) as resp:
retn = await resp.json()
self.eq('ok', retn.get('status'))

self.true(newb.locked)

async with sess.get(f'https://localhost:{port}/api/v1/auth/users') as resp:
item = await resp.json()
users = item.get('result')
self.notin('newb', [u.get('name') for u in users])

async with sess.get(f'https://localhost:{port}/api/v1/auth/users?archived=asdf') as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('BadHttpParam', item.get('code'))

async with sess.get(f'https://localhost:{port}/api/v1/auth/users?archived=99') as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('BadHttpParam', item.get('code'))

async with sess.get(f'https://localhost:{port}/api/v1/auth/users?archived=0') as resp:
item = await resp.json()
users = item.get('result')
self.notin('newb', [u.get('name') for u in users])

async with sess.get(f'https://localhost:{port}/api/v1/auth/users?archived=1') as resp:
item = await resp.json()
users = item.get('result')
self.isin('newb', [u.get('name') for u in users])

info = {'archived': False}
async with sess.post(f'https://localhost:{port}/api/v1/auth/user/{newb.iden}', json=info) as resp:
retn = await resp.json()
self.eq('ok', retn.get('status'))

async with sess.get(f'https://localhost:{port}/api/v1/auth/users') as resp:
item = await resp.json()
users = item.get('result')
self.isin('newb', [u.get('name') for u in users])

async def test_http_delrole(self):

async with self.getTestCore() as core:

host, port = await core.addHttpsPort(0, host='127.0.0.1')

root = core.auth.getUserByName('root')
await root.setPasswd('secret')

newb = await core.auth.addUser('bob')
await newb.setPasswd('secret')

bobs = await core.auth.addRole('bobs')

await newb.grant('bobs')

async with self.getHttpSess() as sess:

info = {'name': 'bobs'}
async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', json=info) as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('NotAuthenticated', item.get('code'))

async with self.getHttpSess(auth=('bob', 'secret'), port=port) as sess:

info = {'name': 'bobs'}
async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', json=info) as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('AuthDeny', item.get('code'))

async with self.getHttpSess(auth=('root', 'secret'), port=port) as sess:

info = {}
async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', json=info) as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('MissingField', item.get('code'))

async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', data=b'asdf') as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('BadJson', item.get('code'))

info = {'name': 'newp'}
async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', json=info) as resp:
item = await resp.json()
self.eq('err', item.get('status'))
self.eq('NoSuchRole', item.get('code'))

info = {'name': 'bobs'}
async with sess.post(f'https://localhost:{port}/api/v1/auth/delrole', json=info) as resp:
item = await resp.json()
self.eq('ok', item.get('status'))

self.len(0, newb.getRoles())
self.none(core.auth.getRoleByName('bobs'))

async def test_http_auth(self):
'''
Test the HTTP api for cell auth.
Expand Down
16 changes: 15 additions & 1 deletion synapse/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,10 +989,24 @@ def getAsyncLoggerStream(self, logname, mesg=''):
slogger.removeHandler(handler)

@contextlib.asynccontextmanager
async def getHttpSess(self):
async def getHttpSess(self, auth=None, port=None):

jar = aiohttp.CookieJar(unsafe=True)
conn = aiohttp.TCPConnector(ssl=False)

async with aiohttp.ClientSession(cookie_jar=jar, connector=conn) as sess:

if auth is not None:

if port is None: # pragma: no cover
raise Exception('getHttpSess requires port for auth')

user, passwd = auth
async with sess.post(f'https://localhost:{port}/api/v1/login', json={'user': user, 'passwd': passwd}) as resp:
retn = await resp.json()
self.eq('ok', retn.get('status'))
self.eq(user, retn['result']['name'])

yield sess

@contextlib.contextmanager
Expand Down

0 comments on commit 8518e46

Please sign in to comment.