text: from __future__ import absolute_import
  
  import six
  import pytz
  
  from datetime import datetime
  
  from sentry import analytics
  from sentry.coreapi import APIUnauthorized
  from sentry.mediators import Mediator, Param
  from sentry.mediators.token_exchange.validator import Validator
  from sentry.mediators.token_exchange.util import token_expiration
  from sentry.models import ApiApplication, ApiToken, ApiGrant, SentryApp
  from sentry.utils.cache import memoize
  
  
  class GrantExchanger(Mediator):
      """
      Exchanges a Grant Code for an Access Token
      """
  
      install = Param("sentry.models.SentryAppInstallation")
      code = Param(six.string_types)
      client_id = Param(six.string_types)
      user = Param("sentry.models.User")
  
      def call(self):
          self._validate()
          self._create_token()
  
          # Once it's exchanged it's no longer valid and should not be
          # exchangable, so we delete it.
          self._delete_grant()
  
          return self.token
  
      def record_analytics(self):
          analytics.record(
              "sentry_app.token_exchanged",
              sentry_app_installation_id=self.install.id,
              exchange_type="authorization",
          )
  
      def _validate(self):
          Validator.run(install=self.install, client_id=self.client_id, user=self.user)
  
          if not self._grant_belongs_to_install() or not self._sentry_app_user_owns_grant():
              raise APIUnauthorized
  
          if not self._grant_is_active():
              raise APIUnauthorized("Grant has already expired.")
  
      def _grant_belongs_to_install(self):
          return self.grant.sentry_app_installation == self.install
  
      def _sentry_app_user_owns_grant(self):
          return self.grant.application.owner == self.user
  
      def _grant_is_active(self):
          return self.grant.expires_at > datetime.now(pytz.UTC)
  
      def _delete_grant(self):
          self.grant.delete()
  
      def _create_token(self):
          self.token = ApiToken.objects.create(
              user=self.user,
              application=self.application,
              scope_list=self.sentry_app.scope_list,
              expires_at=token_expiration(),
          )
          self.install.api_token = self.token
          self.install.save()
  
      @memoize
      def grant(self):
          try:
              return (
                  ApiGrant.objects.select_related("sentry_app_installation")
                  .select_related("application")
                  .select_related("application__sentry_app")
                  .get(code=self.code)
              )
          except ApiGrant.DoesNotExist:
              raise APIUnauthorized
  
      @property
      def application(self):
          try:
              return self.grant.application
          except ApiApplication.DoesNotExist:
              raise APIUnauthorized
  
      @property
      def sentry_app(self):
          try:
              return self.application.sentry_app
          except SentryApp.DoesNotExist:
              raise APIUnauthorized
  