api.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. from flask import request, Response
  2. from flask_appbuilder.api import BaseApi, safe
  3. from flask_appbuilder.const import (
  4. API_SECURITY_ACCESS_TOKEN_KEY,
  5. API_SECURITY_PROVIDER_DB,
  6. API_SECURITY_PROVIDER_LDAP,
  7. API_SECURITY_REFRESH_TOKEN_KEY,
  8. API_SECURITY_VERSION,
  9. )
  10. from flask_appbuilder.security.schemas import login_post
  11. from flask_appbuilder.views import expose
  12. from flask_jwt_extended import (
  13. create_access_token,
  14. create_refresh_token,
  15. get_jwt_identity,
  16. jwt_required,
  17. )
  18. from marshmallow import ValidationError
  19. class SecurityApi(BaseApi):
  20. resource_name = "security"
  21. version = API_SECURITY_VERSION
  22. openapi_spec_tag = "Security"
  23. def add_apispec_components(self, api_spec):
  24. super(SecurityApi, self).add_apispec_components(api_spec)
  25. jwt_scheme = {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}
  26. api_spec.components.security_scheme("jwt", jwt_scheme)
  27. api_spec.components.security_scheme("jwt_refresh", jwt_scheme)
  28. @expose("/login", methods=["POST"])
  29. @safe
  30. def login(self) -> Response:
  31. """Login endpoint for the API returns a JWT and optionally a refresh token
  32. ---
  33. post:
  34. description: >-
  35. Authenticate and get a JWT access and refresh token
  36. requestBody:
  37. required: true
  38. content:
  39. application/json:
  40. schema:
  41. type: object
  42. properties:
  43. username:
  44. description: The username for authentication
  45. example: admin
  46. type: string
  47. password:
  48. description: The password for authentication
  49. example: complex-password
  50. type: string
  51. provider:
  52. description: Choose an authentication provider
  53. example: db
  54. type: string
  55. enum:
  56. - db
  57. - ldap
  58. refresh:
  59. description: If true a refresh token is provided also
  60. example: true
  61. type: boolean
  62. responses:
  63. 200:
  64. description: Authentication Successful
  65. content:
  66. application/json:
  67. schema:
  68. type: object
  69. properties:
  70. access_token:
  71. type: string
  72. refresh_token:
  73. type: string
  74. 400:
  75. $ref: '#/components/responses/400'
  76. 401:
  77. $ref: '#/components/responses/401'
  78. 500:
  79. $ref: '#/components/responses/500'
  80. """
  81. if not request.is_json:
  82. return self.response_400(message="Request payload is not JSON")
  83. try:
  84. login_payload = login_post.load(request.json)
  85. except ValidationError as error:
  86. return self.response_400(message=error.messages)
  87. # AUTH
  88. user = None
  89. if login_payload["provider"] == API_SECURITY_PROVIDER_DB:
  90. user = self.appbuilder.sm.auth_user_db(
  91. login_payload["username"], login_payload["password"]
  92. )
  93. elif login_payload["provider"] == API_SECURITY_PROVIDER_LDAP:
  94. user = self.appbuilder.sm.auth_user_ldap(
  95. login_payload["username"], login_payload["password"]
  96. )
  97. if not user:
  98. return self.response_401()
  99. # Identity can be any data that is json serializable
  100. resp = dict()
  101. resp[API_SECURITY_ACCESS_TOKEN_KEY] = create_access_token(
  102. identity=user.id, fresh=True
  103. )
  104. if "refresh" in login_payload and login_payload["refresh"]:
  105. resp[API_SECURITY_REFRESH_TOKEN_KEY] = create_refresh_token(
  106. identity=user.id
  107. )
  108. return self.response(200, **resp)
  109. @expose("/refresh", methods=["POST"])
  110. @jwt_required(refresh=True)
  111. @safe
  112. def refresh(self) -> Response:
  113. """
  114. Security endpoint for the refresh token, so we can obtain a new
  115. token without forcing the user to login again
  116. ---
  117. post:
  118. description: >-
  119. Use the refresh token to get a new JWT access token
  120. responses:
  121. 200:
  122. description: Refresh Successful
  123. content:
  124. application/json:
  125. schema:
  126. type: object
  127. properties:
  128. access_token:
  129. description: A new refreshed access token
  130. type: string
  131. 401:
  132. $ref: '#/components/responses/401'
  133. 500:
  134. $ref: '#/components/responses/500'
  135. security:
  136. - jwt_refresh: []
  137. """
  138. resp = {
  139. API_SECURITY_ACCESS_TOKEN_KEY: create_access_token(
  140. identity=get_jwt_identity(), fresh=False
  141. )
  142. }
  143. return self.response(200, **resp)