Skip to content

Potentially handle logout differently #1

@wparad

Description

@wparad
  public async generateLogoutRequest(user: Profile, options: LogoutOptions) : Promise<string> {
    const id = options.generateUniqueId();
    const instant = generateInstant();

    const request = {
      "samlp:LogoutRequest": {
        "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
        "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
        "@ID": id,
        "@Version": "2.0",
        "@IssueInstant": instant,
        "@Destination": options.logoutUrl,
        "saml:Issuer": {
          "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
          "#text": options.issuer,
        },
        "saml:NameID": {
          "@Format": user!.nameIDFormat,
          "#text": user!.nameID,
        },
      },
    } as LogoutRequestXML;

    if (user!.nameQualifier != null) {
      request["samlp:LogoutRequest"]["saml:NameID"]["@NameQualifier"] = user!.nameQualifier;
    }

    if (user!.spNameQualifier != null) {
      request["samlp:LogoutRequest"]["saml:NameID"]["@SPNameQualifier"] = user!.spNameQualifier;
    }

    if (user!.sessionIndex) {
      request["samlp:LogoutRequest"]["saml2p:SessionIndex"] = {
        "@xmlns:saml2p": "urn:oasis:names:tc:SAML:2.0:protocol",
        "#text": user!.sessionIndex,
      };
    }

    await this.cacheProvider.save(id, instant);
    const request = buildXmlBuilderObject(request, false);
    await this._requestToUrl(request, null, "logout");
  }

  public async generateLogoutResponse(user: Profile, options: LogoutOptions) : Promise<string> {
    const id = options.generateUniqueId();
    const instant = generateInstant();

    const request = {
      "samlp:LogoutResponse": {
        "@xmlns:samlp": "urn:oasis:names:tc:SAML:2.0:protocol",
        "@xmlns:saml": "urn:oasis:names:tc:SAML:2.0:assertion",
        "@ID": id,
        "@Version": "2.0",
        "@IssueInstant": instant,
        "@Destination": options.logoutUrl,
        "@InResponseTo": logoutRequest.ID,
        "saml:Issuer": {
          "#text": options.issuer,
        },
        "samlp:Status": {
          "samlp:StatusCode": {
            "@Value": "urn:oasis:names:tc:SAML:2.0:status:Success",
          },
        },
      },
    };

    return buildXmlBuilderObject(request, false);
  }

  private async _requestToUrl(
    request: string | null | undefined,
    response: string | null,
    operation: string,
    additionalParameters: querystring.ParsedUrlQuery
  ): Promise<string> {
    providerSingleSignOnUrl = assertRequired(options.providerSingleSignOnUrl, "providerSingleSignOnUrl is required");

    let buffer: Buffer;
    if (options.skipRequestCompression) {
      buffer = Buffer.from((request || response)!, "utf8");
    } else {
      buffer = await deflateRaw((request || response)!);
    }

    const base64 = buffer.toString("base64");
    let target = new URL(providerSingleSignOnUrl);

    if (operation === "logout") {
      if (options.logoutUrl) {
        target = new URL(options.logoutUrl);
      }
    } else if (operation !== "authorize") {
      throw new Error("Unknown operation: " + operation);
    }

    const samlMessage: querystring.ParsedUrlQuery = request
      ? {
          SAMLRequest: base64,
        }
      : {
          SAMLResponse: base64,
        };
    Object.keys(additionalParameters).forEach((k) => {
      samlMessage[k] = additionalParameters[k];
    });
    if (options.privateKey != null) {
      if (!providerSingleSignOnUrl) {
        throw new Error('"providerSingleSignOnUrl" config parameter is required for signed messages');
      }

      // sets .SigAlg and .Signature
      this.signRequest(samlMessage);
    }
    Object.keys(samlMessage).forEach((k) => {
      target.searchParams.set(k, samlMessage[k] as string);
    });

    return target.toString();
  }

  public async getLogoutResponseUrl(options: LogoutResponseOptions) : Promise<string> {
    const response = this._generateLogoutResponse(samlLogoutRequest);
    return await this._requestToUrl(null, response, 'logout');
  }

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions