comparison doc/rest.txt @ 5893:13f5ac918120

fix spacing on example code and typo fix.
author John Rouillard <rouilj@ieee.org>
date Wed, 02 Oct 2019 20:55:15 -0400
parents afb5705d1fe5
children 45a104bb127e
comparison
equal deleted inserted replaced
5892:afb5705d1fe5 5893:13f5ac918120
1437 perm = db.security.addPermission(name='Create', klass='timelog', 1437 perm = db.security.addPermission(name='Create', klass='timelog',
1438 description="Allow timelog creation", props_only=False) 1438 description="Allow timelog creation", props_only=False)
1439 db.security.addPermissionToRole("User:timelog", perm) 1439 db.security.addPermissionToRole("User:timelog", perm)
1440 1440
1441 perm = db.security.addPermission(name='View', klass='issue', 1441 perm = db.security.addPermission(name='View', klass='issue',
1442 properties=('id', 'times'), 1442 properties=('id', 'times'),
1443 description="Allow timelog retreival for issue", 1443 description="Allow retrieving issue etag or timelog issue",
1444 props_only=False) 1444 props_only=False)
1445 db.security.addPermissionToRole("User:timelog", perm) 1445 db.security.addPermissionToRole("User:timelog", perm)
1446 1446
1447 perm = db.security.addPermission(name='Edit', klass='issue', 1447 perm = db.security.addPermission(name='Edit', klass='issue',
1448 properties=('id', 'times'), 1448 properties=('id', 'times'),
1449 description="Allow editing timelog for issue", props_only=False) 1449 description="Allow editing timelog for issue",
1450 props_only=False)
1450 db.security.addPermissionToRole("User:timelog", perm) 1451 db.security.addPermissionToRole("User:timelog", perm)
1451 1452
1452 The role is named to work with the /rest/jwt/issue rest endpoint 1453 The role is named to work with the /rest/jwt/issue rest endpoint
1453 defined below. Starting the role name with ``User:`` allows the jwt 1454 defined below. Starting the role name with ``User:`` allows the jwt
1454 issue code to create a token with this role if the user requesting the 1455 issue code to create a token with this role if the user requesting the
1455 role has the User role. 1456 role has the User role.
1456 1457
1457 The role *must* have access to the issue ``id`` to retrieve the etag for 1458 The role *must* have access to the issue ``id`` to retrieve the etag for
1458 the issue. The etag is passed in the ``If-Match`` HTTP header when you 1459 the issue. The etag is passed in the ``If-Match`` HTTP header when you
1459 make a call to patch or update the ``timess` property of the issue. 1460 make a call to patch or update the ``times` property of the issue.
1460 1461
1461 If you use a PATCH rest call with "@op=add" to append the new timelog, 1462 If you use a PATCH rest call with "@op=add" to append the new timelog,
1462 you don't need View access to the ``times`` property. If you replace the 1463 you don't need View access to the ``times`` property. If you replace the
1463 ``times`` value, you need to read the current value of ``times`` (using 1464 ``times`` value, you need to read the current value of ``times`` (using
1464 View permission), append the newly created timelog id to the (array) 1465 View permission), append the newly created timelog id to the (array)
1475 only been tested with python3):: 1476 only been tested with python3)::
1476 1477
1477 from roundup.rest import Routing, RestfulInstance, _data_decorator 1478 from roundup.rest import Routing, RestfulInstance, _data_decorator
1478 1479
1479 class RestfulInstance(object): 1480 class RestfulInstance(object):
1480 @Routing.route("/jwt/issue", 'POST') 1481 @Routing.route("/jwt/issue", 'POST')
1481 @_data_decorator 1482 @_data_decorator
1482 def generate_jwt(self, input): 1483 def generate_jwt(self, input):
1483 import jwt 1484 import jwt
1484 import datetime 1485 import datetime
1485 from roundup.anypy.strings import b2s 1486 from roundup.anypy.strings import b2s
1486 1487
1487 # require basic auth to generate a token 1488 # require basic auth to generate a token
1488 # At some point we can support a refresh token. 1489 # At some point we can support a refresh token.
1489 # maybe a jwt with the "refresh": True claim generated 1490 # maybe a jwt with the "refresh": True claim generated
1490 # using: "refresh": True in the json request payload. 1491 # using: "refresh": True in the json request payload.
1491 1492
1492 denialmsg='Token creation requires login with basic auth.' 1493 denialmsg='Token creation requires login with basic auth.'
1493 if 'HTTP_AUTHORIZATION' in self.client.env: 1494 if 'HTTP_AUTHORIZATION' in self.client.env:
1494 try: 1495 try:
1495 auth = self.client.env['HTTP_AUTHORIZATION'] 1496 auth = self.client.env['HTTP_AUTHORIZATION']
1496 scheme, challenge = auth.split(' ', 1) 1497 scheme, challenge = auth.split(' ', 1)
1497 except (ValueError, AttributeError): 1498 except (ValueError, AttributeError):
1498 # bad format for header 1499 # bad format for header
1499 raise Unauthorised(denialmsg) 1500 raise Unauthorised(denialmsg)
1500 if scheme.lower() != 'basic': 1501 if scheme.lower() != 'basic':
1501 raise Unauthorised(denialmsg) 1502 raise Unauthorised(denialmsg)
1502 else: 1503 else:
1503 raise Unauthorised(denialmsg) 1504 raise Unauthorised(denialmsg)
1504 1505
1505 # If we reach this point we have validated that the user has 1506 # If we reach this point we have validated that the user has
1506 # logged in with a password using basic auth. 1507 # logged in with a password using basic auth.
1507 all_roles = list(self.db.security.role.items()) 1508 all_roles = list(self.db.security.role.items())
1508 rolenames = [] 1509 rolenames = []
1509 for role in all_roles: 1510 for role in all_roles:
1510 rolenames.append(role[0]) 1511 rolenames.append(role[0])
1511 1512
1512 user_roles = list(self.db.user.get_roles(self.db.getuid())) 1513 user_roles = list(self.db.user.get_roles(self.db.getuid()))
1513 1514
1514 claim= { 'sub': self.db.getuid(), 1515 claim= { 'sub': self.db.getuid(),
1515 'iss': self.db.config.TRACKER_WEB, 1516 'iss': self.db.config.TRACKER_WEB,
1516 'aud': self.db.config.TRACKER_WEB, 1517 'aud': self.db.config.TRACKER_WEB,
1517 'iat': datetime.datetime.utcnow(), 1518 'iat': datetime.datetime.utcnow(),
1518 } 1519 }
1519 1520
1520 lifetime = 0 1521 lifetime = 0
1521 if 'lifetime' in input: 1522 if 'lifetime' in input:
1522 if input['lifetime'].value != 'unlimited': 1523 if input['lifetime'].value != 'unlimited':
1523 try: 1524 try:
1524 lifetime = datetime.timedelta(seconds=int(input['lifetime'].value)) 1525 lifetime = datetime.timedelta(seconds=int(input['lifetime'].value))
1525 except ValueError: 1526 except ValueError:
1526 raise UsageError("Value 'lifetime' must be 'unlimited' or an integer to specify" + 1527 raise UsageError("Value 'lifetime' must be 'unlimited' or an integer to specify" +
1527 " lifetime in seconds. Got %s."%input['lifetime'].value) 1528 " lifetime in seconds. Got %s."%input['lifetime'].value)
1528 else: 1529 else:
1529 lifetime = datetime.timedelta(seconds=86400) # 1 day by default 1530 lifetime = datetime.timedelta(seconds=86400) # 1 day by default
1530 1531
1531 if lifetime: # if lifetime = 0 make unlimited by omitting exp claim 1532 if lifetime: # if lifetime = 0 make unlimited by omitting exp claim
1532 claim['exp'] = datetime.datetime.utcnow() + lifetime 1533 claim['exp'] = datetime.datetime.utcnow() + lifetime
1533 1534
1534 newroles = [] 1535 newroles = []
1535 if 'roles' in input: 1536 if 'roles' in input:
1536 for role in input['roles'].value: 1537 for role in input['roles'].value:
1537 if role not in rolenames: 1538 if role not in rolenames:
1538 raise UsageError("Role %s is not valid."%role) 1539 raise UsageError("Role %s is not valid."%role)
1539 if role in user_roles: 1540 if role in user_roles:
1540 newroles.append(role) 1541 newroles.append(role)
1541 continue 1542 continue
1542 parentrole = role.split(':', 1)[0] 1543 parentrole = role.split(':', 1)[0]
1543 if parentrole in user_roles: 1544 if parentrole in user_roles:
1544 newroles.append(role) 1545 newroles.append(role)
1545 continue 1546 continue
1546 1547
1547 raise UsageError("Role %s is not permitted."%role) 1548 raise UsageError("Role %s is not permitted."%role)
1548 1549
1549 claim['roles'] = newroles 1550 claim['roles'] = newroles
1550 else: 1551 else:
1551 claim['roles'] = user_roles 1552 claim['roles'] = user_roles
1552 secret = self.db.config.WEB_JWT_SECRET 1553 secret = self.db.config.WEB_JWT_SECRET
1553 myjwt = jwt.encode(claim, secret, algorithm='HS256') 1554 myjwt = jwt.encode(claim, secret, algorithm='HS256')
1554 1555
1555 result = {"jwt": b2s(myjwt), 1556 result = {"jwt": b2s(myjwt),
1556 } 1557 }
1557 1558
1558 return 200, result 1559 return 200, result
1559 1560
1560 @Routing.route("/jwt/validate", 'GET') 1561 @Routing.route("/jwt/validate", 'GET')
1561 @_data_decorator 1562 @_data_decorator
1562 def validate_jwt(self,input): 1563 def validate_jwt(self,input):
1563 import jwt 1564 import jwt
1564 if not 'jwt' in input: 1565 if not 'jwt' in input:
1565 raise UsageError("jwt key must be specified") 1566 raise UsageError("jwt key must be specified")
1566 1567
1567 myjwt = input['jwt'].value 1568 myjwt = input['jwt'].value
1568 1569
1569 secret = self.db.config.WEB_JWT_SECRET 1570 secret = self.db.config.WEB_JWT_SECRET
1570 try: 1571 try:
1571 result = jwt.decode(myjwt, secret, 1572 result = jwt.decode(myjwt, secret,
1572 algorithms=['HS256'], 1573 algorithms=['HS256'],
1573 audience=self.db.config.TRACKER_WEB, 1574 audience=self.db.config.TRACKER_WEB,
1574 issuer=self.db.config.TRACKER_WEB, 1575 issuer=self.db.config.TRACKER_WEB,
1575 ) 1576 )
1576 except jwt.exceptions.InvalidTokenError as err: 1577 except jwt.exceptions.InvalidTokenError as err:
1577 return 401, str(err) 1578 return 401, str(err)
1578 1579
1579 return 200, result 1580 return 200, result
1580 1581
1581 **Note this is sample code. Use at your own risk.** It breaks a few 1582 **Note this is sample code. Use at your own risk.** It breaks a few
1582 rules about jwts (e.g. it allows you to make unlimited lifetime 1583 rules about jwts (e.g. it allows you to make unlimited lifetime
1583 jwts). If you subscribe to the concept of jwt refresh tokens, this code 1584 jwts). If you subscribe to the concept of jwt refresh tokens, this code
1584 will have to be changed as it will only generate jwts with 1585 will have to be changed as it will only generate jwts with

Roundup Issue Tracker: http://roundup-tracker.org/