All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.biz.tenants.SAMLController Maven / Gradle / Ivy

There is a newer version: 9.6
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.biz.tenants;

import sirius.biz.web.BizController;
import sirius.db.mixing.constraints.FieldOperator;
import sirius.kernel.commons.Lambdas;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.std.ConfigValue;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Register;
import sirius.kernel.health.Exceptions;
import sirius.web.controller.Controller;
import sirius.web.controller.Routed;
import sirius.web.http.WebContext;
import sirius.web.security.SAMLHelper;
import sirius.web.security.SAMLResponse;
import sirius.web.security.UserContext;
import sirius.web.security.UserInfo;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

/**
 * Permis a login via SAML.
 */
@Register(classes = Controller.class)
public class SAMLController extends BizController {

    @Part
    private SAMLHelper saml;

    @ConfigValue("product.wondergemRoot")
    private String wondergemRoot;

    @ConfigValue("security.roles")
    private List roles;

    /**
     * Lists all possible SAML tenants or permits to create a login form for a custom SAML provider.
     *
     * @param ctx the current request
     */
    @Routed("/saml")
    public void saml(WebContext ctx) {
        List tenants = obtainTenantsForSaml(ctx);

        ctx.respondWith().template("/templates/tenants/saml.html.pasta", tenants);
    }

    private List obtainTenantsForSaml(WebContext ctx) {
        // If GET parameters are present, we create a "fake" tenant to provide a custom SAML target.
        // This can be used if several identity providers are available for a single tenant.
        // We can verify several tenants but we can only redirect to a single identity provider.
        // Therefore these parameters can be used to create a SAML request to a custom one.
        if (ctx.hasParameter("issuerName")) {
            Tenant fakeTenant = new Tenant();
            fakeTenant.setSamlRequestIssuerName(ctx.require("issuerName").asString());
            fakeTenant.setSamlIssuerUrl(ctx.require("issuerUrl").asString().replace("javascript:", ""));
            fakeTenant.setSamlIssuerIndex(ctx.get("issuerIndex").asString("0"));

            return Collections.singletonList(fakeTenant);
        }

        return oma.select(Tenant.class)
                  .where(FieldOperator.on(Tenant.SAML_ISSUER_URL).notEqual(null))
                  .orderAsc(Tenant.NAME)
                  .queryList();
    }

    /**
     * Processes a SAML response and tries to create or update a user which is then logged in.
     *
     * @param ctx the SAML response as request
     */
    @Routed("/saml/login")
    public void samlLogin(WebContext ctx) {
        if (!ctx.isPOST()) {
            ctx.respondWith().redirectToGet("/saml");
            return;
        }

        SAMLResponse response = saml.parseSAMLResponse(ctx);

        if (Strings.isEmpty(response.getNameId())) {
            throw Exceptions.createHandled()
                            .withSystemErrorMessage("SAML Error: No or empty name ID was given.")
                            .handle();
        }

        TenantUserManager manager = (TenantUserManager) UserContext.getCurrentScope().getUserManager();
        UserInfo user = manager.findUserByName(ctx, response.getNameId());

        if (user == null) {
            user = tryCreateUser(ctx, response);
        } else {
            verifyUser(response, user);
        }

        UserContext userContext = UserContext.get();
        userContext.setCurrentUser(user);
        userContext.attachUserToSession();
        manager.recordLogin(user, true);

        ctx.respondWith().redirectToGet(ctx.get("goto").asString(wondergemRoot));
    }

    private UserInfo tryCreateUser(WebContext ctx, SAMLResponse response) {
        if (Strings.isEmpty(response.getIssuer())) {
            throw Exceptions.createHandled().withSystemErrorMessage("SAML Error: No issuer in request!").handle();
        }

        Tenant tenant = findTenant(response);

        checkFingerprint(tenant, response);
        UserAccount account = new UserAccount();
        account.getTenant().setValue(tenant);
        account.getLogin().setUsername(response.getNameId());
        account.getLogin().setCleartextPassword(UUID.randomUUID().toString());
        account.setExternalLoginRequired(true);
        updateAccount(response, account);
        oma.update(account);

        TenantUserManager manager = (TenantUserManager) UserContext.getCurrentScope().getUserManager();
        UserInfo user = manager.findUserByName(ctx, response.getNameId());
        if (user == null) {
            throw Exceptions.createHandled().withSystemErrorMessage("SAML Error: Failed to create a user").handle();
        }

        return user;
    }

    private Tenant findTenant(SAMLResponse response) {
        for (Tenant tenant : oma.select(Tenant.class)
                                .fields(Tenant.ID, Tenant.SAML_ISSUER_NAME, Tenant.SAML_FINGERPRINT)
                                .where(FieldOperator.on(Tenant.SAML_ISSUER_NAME).notEqual(null))
                                .where(FieldOperator.on(Tenant.SAML_FINGERPRINT).notEqual(null))
                                .queryList()) {
            if (checkIssuer(tenant, response)) {
                return tenant;
            }
        }

        throw Exceptions.createHandled()
                        .withSystemErrorMessage("SAML Error: No matching tenant found for issuer: %s",
                                                response.getIssuer())
                        .handle();
    }

    private void verifyUser(SAMLResponse response, UserInfo user) {
        UserAccount account = user.getUserObject(UserAccount.class);
        Tenant tenant = account.getTenant().getValue();

        if (!checkIssuer(tenant, response)) {
            throw Exceptions.createHandled().withSystemErrorMessage("SAML Error: Issuer mismatch!").handle();
        }
        if (!checkFingerprint(tenant, response)) {
            throw Exceptions.createHandled().withSystemErrorMessage("SAML Error: Fingerprint mismatch!").handle();
        }

        account = oma.refreshOrFail(account);
        updateAccount(response, account);
        oma.update(account);
    }

    private boolean checkFingerprint(Tenant tenant, SAMLResponse response) {
        return isInList(tenant.getSamlFingerprint(), response.getFingerprint());
    }

    private boolean checkIssuer(Tenant tenant, SAMLResponse response) {
        return isInList(tenant.getSamlIssuerName(), response.getIssuer());
    }

    private boolean isInList(String values, String valueToCheck) {
        if (Strings.isEmpty(values)) {
            return false;
        }

        for (String value : values.split(",")) {
            if (Strings.isFilled(value) && Strings.areEqual(value, valueToCheck)) {
                return true;
            }
        }

        return false;
    }

    private void updateAccount(SAMLResponse response, UserAccount account) {
        account.getPermissions().getPermissions().clear();
        response.getAttribute(SAMLResponse.ATTRIBUTE_GROUP)
                .stream()
                .filter(Strings::isFilled)
                .flatMap(value -> Arrays.stream(value.split(",")))
                .map(String::trim)
                .filter(Strings::isFilled)
                .filter(role -> roles.contains(role))
                .collect(Lambdas.into(account.getPermissions().getPermissions()));

        if (Strings.isFilled(response.getAttributeValue(SAMLResponse.ATTRIBUTE_GIVEN_NAME))) {
            account.getPerson().setFirstname(response.getAttributeValue(SAMLResponse.ATTRIBUTE_GIVEN_NAME));
        }
        if (Strings.isFilled(response.getAttributeValue(SAMLResponse.ATTRIBUTE_SURNAME))) {
            account.getPerson().setLastname(response.getAttributeValue(SAMLResponse.ATTRIBUTE_SURNAME));
        }
        if (Strings.isFilled(response.getAttributeValue(SAMLResponse.ATTRIBUTE_EMAIL_ADDRESS))) {
            account.setEmail(response.getAttributeValue(SAMLResponse.ATTRIBUTE_EMAIL_ADDRESS));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy