Exemplo n.º 1
0
    def get_device(self, provider=None, user_code=None, client_id=None):
        """The device authorization endpoint can be used to request device and user codes.
        This endpoint is used to start the device flow authorization process and user code verification.

        User code confirmation::

          GET LOCATION/device/<provider>?user_code=<user code>

        Response::

          HTTP/1.1 200 OK
        """

        if user_code:
            # If received a request with a user code, then prepare a request to authorization endpoint.
            self.log.verbose("User code verification.")
            result = self.server.db.getSessionByUserCode(user_code)
            if not result["OK"] or not result["Value"]:
                return getHTML(
                    "session is expired.",
                    theme="warning",
                    body=result.get("Message"),
                    info="Seems device code flow authorization session %s expired." % user_code,
                )
            session = result["Value"]
            # Get original request from session
            req = createOAuth2Request(dict(method="GET", uri=session["uri"]))
            req.setQueryArguments(id=session["id"], user_code=user_code)

            # Save session to cookie and redirect to authorization endpoint
            authURL = "%s?%s" % (req.path.replace("device", "authorization"), req.query)
            return self.server.handle_response(302, {}, [("Location", authURL)], session)

        # If received a request without a user code, then send a form to enter the user code
        with dom.div(cls="row mt-5 justify-content-md-center") as tag:
            with dom.div(cls="col-auto"):
                dom.div(
                    dom.form(
                        dom._input(type="text", name="user_code"),
                        dom.button("Submit", type="submit", cls="btn btn-submit"),
                        action=self.currentPath,
                        method="GET",
                    ),
                    cls="card",
                )
        return getHTML(
            "user code verification..",
            body=tag,
            icon="ticket-alt",
            info="Device flow required user code. You will need to type user code to continue.",
        )
Exemplo n.º 2
0
    def prepare(self):
        self.set_status(404)
        from DIRAC.FrameworkSystem.private.authorization.utils.Utilities import getHTML

        self.finish(
            getHTML("Not found.",
                    state=404,
                    info="Nothing matches the given URI."))
Exemplo n.º 3
0
    def create_authorization_response(self, redirect_uri, user):
        """Mark session as authed with received user

        :param str redirect_uri: redirect uri
        :param dict user: dictionary with username and userID

        :return: result of `handle_response`
        """
        result = self.server.db.getSessionByUserCode(
            self.request.data["user_code"])
        if not result["OK"]:
            return 500, getHTML(
                "server error",
                theme="error",
                body=result["Message"],
                info="Failed to read %s authorization session." %
                self.request.data["user_code"],
            )
        data = result["Value"]
        data.update(
            dict(user_id=user["ID"],
                 uri=self.request.uri,
                 username=user["username"],
                 scope=self.request.scope))
        # Save session with user
        result = self.server.db.updateSession(data, data["id"])
        if not result["OK"]:
            return 500, getHTML(
                "server error",
                theme="error",
                body=result["Message"],
                info="Failed to save %s authorization session status." %
                self.request.data["user_code"],
            )

        # Notify user that authorization completed.
        return 200, getHTML(
            "authorization complete!",
            theme="success",
            info=
            "Authorization has been completed, now you can close this window and return to the terminal.",
        )
Exemplo n.º 4
0
    def validate_consent_request(self, request, provider=None):
        """Validate current HTTP request for authorization page. This page
        is designed for resource owner to grant or deny the authorization::

        :param object request: tornado request
        :param provider: provider

        :return: response generated by `handle_response` or S_ERROR or html
        """
        try:
            request = self.create_oauth2_request(request)
            # Check Identity Provider
            req = self.validateIdentityProvider(request, provider)

            # If return HTML page with IdP selector
            if isinstance(req, str):
                return req

            sLog.info("Validate consent request for ", req.state)
            grant = self.get_authorization_grant(req)
            sLog.debug("Use grant:", grant.GRANT_TYPE)
            grant.validate_consent_request()
            if not hasattr(grant, "prompt"):
                grant.prompt = None

            # Submit second auth flow through IdP
            return self.getIdPAuthorization(req.provider, req)

        except OAuth2Error as error:
            self.db.removeSession(request.sessionID)
            code, body, _ = error(None)
            return self.handle_response(
                payload=getHTML(repr(error), state=code, body=body, info="OAuth2 error."), delSession=True
            )
        except Exception as e:
            self.db.removeSession(request.sessionID)
            sLog.exception(e)
            return self.handle_response(
                payload=getHTML("server error", theme="error", body="traceback", info=repr(e)), delSession=True
            )
Exemplo n.º 5
0
    def parseIdPAuthorizationResponse(self, response, session):
        """Fill session by user profile, tokens, comment, OIDC authorize status, etc.
        Prepare dict with user parameters, if DN is absent there try to get it.
        Create new or modify existing DIRAC user and store the session

        :param dict response: authorization response
        :param str session: session

        :return: S_OK(dict)/S_ERROR()
        """
        providerName = session.pop("Provider")
        sLog.debug("Try to parse authentification response from %s:\n" % providerName, pprint.pformat(response))
        # Parse response
        result = self.idps.getIdProvider(providerName)
        if not result["OK"]:
            return result
        idpObj = result["Value"]
        result = idpObj.parseAuthResponse(response, session)
        if not result["OK"]:
            return result

        # FINISHING with IdP
        # As a result of authentication we will receive user credential dictionary
        credDict, payload = result["Value"]

        sLog.debug("Read profile:", pprint.pformat(credDict))
        # Is ID registred?
        result = getUsernameForDN(credDict["DN"])
        if not result["OK"]:
            comment = f"ID {credDict['ID']} is not registred in DIRAC. "
            payload.update(idpObj.getUserProfile().get("Value", {}))
            result = self.__registerNewUser(providerName, payload)

            if result["OK"]:
                comment += "Administrators have been notified about you."
            else:
                comment += "Please, contact the DIRAC administrators."

            # Notify user about problem
            html = getHTML("unregistered user!", info=comment, theme="warning")
            return S_ERROR(html)

        credDict["username"] = result["Value"]

        # Update token for user. This token will be stored separately in the database and
        # updated from time to time. This token will never be transmitted,
        # it will be used to make exchange token requests.
        result = self.tokenCli.updateToken(idpObj.token, credDict["ID"], idpObj.name)
        return S_OK(credDict) if result["OK"] else result
Exemplo n.º 6
0
    def create_authorization_response(self, response, username):
        """Rewrite original Authlib method
        `authlib.authlib.oauth2.rfc6749.authorization_server.create_authorization_response`
        to catch errors and remove authorization session.

        :return: TornadoResponse object
        """
        try:
            response = super().create_authorization_response(response, username)
            response.clear_cookie("auth_session")
            return response
        except Exception as e:
            sLog.exception(e)
            return self.handle_response(
                payload=getHTML("server error", theme="error", body="traceback", info=repr(e)), delSession=True
            )
Exemplo n.º 7
0
    def __researchDIRACGroup(self, extSession, chooseScope, state):
        """Research DIRAC groups for authorized user

        :param dict extSession: ended authorized external IdP session

        :return: -- will return (None, response) to provide error or group selector
                    will return (grant_user, request) to contionue authorization with choosed group
        """
        # Base DIRAC client auth session
        firstRequest = createOAuth2Request(extSession["firstRequest"])
        # Read requested groups by DIRAC client or user
        firstRequest.addScopes(chooseScope)
        # Read already authed user
        username = extSession["authed"]["username"]
        # Requested arguments in first request
        provider = firstRequest.provider
        self.log.debug("Next groups has been found for %s:" % username, ", ".join(firstRequest.groups))

        # Researche Group
        result = getGroupsForUser(username)
        if not result["OK"]:
            return None, self.server.handle_response(
                payload=getHTML("server error", theme="error", info=result["Message"]), delSession=True
            )
        groups = result["Value"]

        validGroups = [
            group for group in groups if (getIdPForGroup(group) == provider) or ("proxy" in firstRequest.scope)
        ]
        if not validGroups:
            return None, self.server.handle_response(
                payload=getHTML(
                    "groups not found.",
                    theme="error",
                    info=f"No groups found for {username} and for {provider} Identity Provider.",
                ),
                delSession=True,
            )

        self.log.debug("The state of %s user groups has been checked:" % username, pprint.pformat(validGroups))

        # If group already defined in first request, just return it
        if firstRequest.groups:
            return extSession["authed"], firstRequest

        # If not and we found only one valid group, apply this group
        if len(validGroups) == 1:
            firstRequest.addScopes(["g:%s" % validGroups[0]])
            return extSession["authed"], firstRequest

        # Else give user chanse to choose group in browser
        with dom.div(cls="row mt-5 justify-content-md-center align-items-center") as tag:
            for group in sorted(validGroups):
                vo, gr = group.split("_")
                with dom.div(cls="col-auto p-2").add(dom.div(cls="card shadow-lg border-0 text-center p-2")):
                    dom.h4(vo.upper() + " " + gr, cls="p-2")
                    dom.a(href="%s?state=%s&chooseScope=g:%s" % (self.currentPath, state, group), cls="stretched-link")

        html = getHTML(
            "group selection..",
            body=tag,
            icon="users",
            info="Dirac use groups to describe permissions. " "You will need to select one of the groups to continue.",
        )

        return None, self.server.handle_response(payload=html, newSession=extSession)
Exemplo n.º 8
0
    def get_redirect(self, state, error=None, error_description="", chooseScope=[]):
        """Redirect endpoint.
        After a user successfully authorizes an application, the authorization server will redirect
        the user back to the application with either an authorization code or access token in the URL.
        The full URL of this endpoint must be registered in the identity provider.

        Read more in `oauth.com <https://www.oauth.com/oauth2-servers/redirect-uris/>`_.
        Specified by `RFC6749 <https://tools.ietf.org/html/rfc6749#section-3.1.2>`_.

        GET LOCATION/redirect

        :param str state: Current IdP session state
        :param str error: IdP error response
        :param str error_description: error description
        :param list chooseScope: to specify new scope(group in our case) (optional)

        :return: S_OK()/S_ERROR()
        """
        # Check current auth session that was initiated for the selected external identity provider
        session = self.get_secure_cookie("auth_session")
        if not session:
            return self.server.handle_response(
                payload=getHTML(
                    "session is expired.",
                    theme="warning",
                    state=400,
                    info="Seems %s session is expired, please, try again." % state,
                ),
                delSession=True,
            )

        sessionWithExtIdP = json.loads(session)
        if state and not sessionWithExtIdP.get("state") == state:
            return self.server.handle_response(
                payload=getHTML(
                    "session is expired.",
                    theme="warning",
                    state=400,
                    info="Seems %s session is expired, please, try again." % state,
                ),
                delSession=True,
            )

        # Try to catch errors if the authorization on the selected identity provider was unsuccessful
        if error:
            provider = sessionWithExtIdP.get("Provider")
            return self.server.handle_response(
                payload=getHTML(
                    error,
                    theme="error",
                    body=error_description,
                    info="Seems %s session is failed on the %s's' side." % (state, provider),
                ),
                delSession=True,
            )

        if not sessionWithExtIdP.get("authed"):
            # Parse result of the second authentication flow
            self.log.info("%s session, parsing authorization response:\n" % state, self.request.uri)

            result = self.server.parseIdPAuthorizationResponse(self.request, sessionWithExtIdP)
            if not result["OK"]:
                if result["Message"].startswith("<!DOCTYPE html>"):
                    return self.server.handle_response(payload=result["Message"], delSession=True)
                return self.server.handle_response(
                    payload=getHTML("server error", state=500, info=result["Message"]), delSession=True
                )
            # Return main session flow
            sessionWithExtIdP["authed"] = result["Value"]

        # Research group
        grant_user, response = self.__researchDIRACGroup(sessionWithExtIdP, chooseScope, state)
        if not grant_user:
            return response

        # RESPONSE to basic DIRAC client request
        resp = self.server.create_authorization_response(response, grant_user)
        if isinstance(resp.payload, str) and not resp.payload.startswith("<!DOCTYPE html>"):
            resp.payload = getHTML("authorization response", state=resp.status_code, body=resp.payload)
        return resp
Exemplo n.º 9
0
    def validateIdentityProvider(self, request, provider):
        """Check if identity provider registred in DIRAC

        :param object request: request
        :param str provider: provider name

        :return: OAuth2Request object or HTML -- new request with provider name or provider selector
        """
        if provider:
            request.provider = provider

        # Find identity provider for group
        groupProvider = getIdPForGroup(request.group) if request.groups else None

        # If requested access token for group that is not registred in any identity provider
        # or the requested provider does not match the group return error
        if request.group and not groupProvider and "proxy" not in request.scope:
            raise Exception("The %s group belongs to the VO that is not tied to any Identity Provider." % request.group)

        sLog.debug("Check if %s identity provider registred in DIRAC.." % request.provider)
        # Research supported IdPs
        result = getProvidersForInstance("Id")
        if not result["OK"]:
            raise Exception(result["Message"])

        idPs = result["Value"]
        if not idPs:
            raise Exception("No identity providers found.")

        if request.provider:
            if request.provider not in idPs:
                raise Exception("%s identity provider is not registered." % request.provider)
            elif groupProvider and request.provider != groupProvider:
                raise Exception(
                    'The %s group Identity Provider is "%s" and not "%s".'
                    % (request.group, groupProvider, request.provider)
                )
            return request

        # If no identity provider is specified, it must be assigned
        if groupProvider:
            request.provider = groupProvider
            return request

        # If only one identity provider is registered, then choose it
        if len(idPs) == 1:
            request.provider = idPs[0]
            return request

        # Choose IdP HTML interface
        with dom.div(cls="row m-5 justify-content-md-center") as tag:
            for idP in idPs:
                result = getProviderInfo(idP)
                if result["OK"]:
                    logo = result["Value"].get("logoURL")
                    with dom.div(cls="col-md-6 p-2").add(dom.div(cls="card shadow-lg h-100 border-0")):
                        with dom.div(cls="row m-2 justify-content-md-center align-items-center h-100"):
                            with dom.div(cls="col-auto"):
                                dom.h2(idP)
                                dom.a(
                                    href="%s/authorization/%s?%s" % (self.LOCATION, idP, request.query),
                                    cls="stretched-link",
                                )
                            if logo:
                                dom.div(dom.img(src=logo, cls="card-img"), cls="col-auto")

        # Render into HTML
        return getHTML(
            "Identity Provider selection..",
            body=tag,
            icon="fingerprint",
            info="Dirac itself is not an Identity Provider. " "You will need to select one to continue.",
        )