Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance SSO integration with additional authentication URLs and… #2036

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions apps/dashboard/src/main/java/com/akto/action/HomeAction.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.akto.action;

import com.akto.dao.UsersDao;
import com.akto.dto.Config;
import com.akto.dto.User;
import com.akto.dto.sso.SAMLConfig;
import com.akto.listener.InitializerListener;
import com.akto.utils.*;
import com.akto.util.DashboardMode;
import com.akto.utils.sso.CustomSamlSettings;
import com.auth0.AuthorizeUrl;
import com.auth0.SessionUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Filters;
import com.onelogin.saml2.authn.AuthnRequest;
import com.onelogin.saml2.settings.Saml2Settings;
import com.opensymphony.xwork2.Action;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
Expand All @@ -20,9 +25,13 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

import static com.akto.action.SignupAction.*;
import static com.akto.filter.UserDetailsFilter.LOGIN_URI;
Expand Down Expand Up @@ -50,14 +59,38 @@ public String verifyEmail(){
public String execute() {

servletRequest.setAttribute("isSaas", InitializerListener.isSaas);
if (GithubLogin.getClientId() != null) {
servletRequest.setAttribute("githubClientId", new String(Base64.getEncoder().encode(GithubLogin.getClientId().getBytes())));
}
if (GithubLogin.getGithubUrl() != null) {
servletRequest.setAttribute("githubUrl", GithubLogin.getGithubUrl());
}
if(DashboardMode.isOnPremDeployment() && OktaLogin.getAuthorisationUrl() != null){
servletRequest.setAttribute("oktaAuthUrl", new String(Base64.getEncoder().encode(OktaLogin.getAuthorisationUrl().getBytes())));
if(DashboardMode.isOnPremDeployment()) {
if (GithubLogin.getGithubUrl() != null) {
servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000");
servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB);
} else if (OktaLogin.getAuthorisationUrl() != null) {
servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl());
servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA);
} else if (Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) {
try {
SAMLConfig samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE);
Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig);
String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Deflater deflater = new Deflater(Deflater.DEFLATED, true);
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
deflaterOutputStream.write(samlRequestXml.getBytes(StandardCharsets.UTF_8));
deflaterOutputStream.close();
String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
String urlEncoded = URLEncoder.encode(base64Encoded, "UTF-8");

servletRequest.setAttribute("azureAuthUrl", samlConfig.getLoginUrl() + "?SAMLRequest=" + urlEncoded + "&RelayState=" + 1000000);
servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE);
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
} else if (Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) {
Config.GoogleConfig googleSamlConfig = (Config.GoogleConfig) Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML);
servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getAuthURI());
servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML);
}
}
if (InitializerListener.aktoVersion != null && InitializerListener.aktoVersion.contains("akto-release-version")) {
servletRequest.setAttribute("AktoVersionGlobal", "");
Expand Down
54 changes: 29 additions & 25 deletions apps/dashboard/src/main/java/com/akto/action/SignupAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ public String registerViaGithub() {
params.put("client_id", githubConfig.getClientId());
params.put("client_secret", githubConfig.getClientSecret());
params.put("code", this.code);
params.put("scope", "user");
logger.info("Github code length: {}", this.code.length());
try {
String githubUrl = githubConfig.getGithubUrl();
Expand Down Expand Up @@ -491,12 +492,17 @@ public String registerViaGithub() {
int refreshTokenExpiry = (int) Double.parseDouble(tokenData.getOrDefault("refresh_token_expires_in", "0").toString());
Map<String,Object> userData = CustomHttpRequest.getRequest(githubApiUrl + "/user", "Bearer " + accessToken);
logger.info("Get request to {} success", githubApiUrl);
String company = "sso";
String username = userData.get("login").toString() + "@" + company;

List<Map<String, String>> emailResp = GithubLogin.getEmailRequest(accessToken);
String username = userData.get("name").toString();
String email = GithubLogin.getPrimaryGithubEmail(emailResp);
if(email == null || email.isEmpty()) {
email = username + "@sso";
}
logger.info("username {}", username);
SignupInfo.GithubSignupInfo ghSignupInfo = new SignupInfo.GithubSignupInfo(accessToken, refreshToken, refreshTokenExpiry, username);
SignupInfo.GithubSignupInfo ghSignupInfo = new SignupInfo.GithubSignupInfo(accessToken, refreshToken, refreshTokenExpiry, email, username);
shouldLogin = "true";
createUserAndRedirect(username, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString());
createUserAndRedirectWithDefaultRole(email, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString());
code = "";
logger.info("Executed registerViaGithub");

Expand Down Expand Up @@ -551,14 +557,8 @@ public String registerViaOkta() throws IOException{
String username = userInfo.get("preferred_username").toString();

SignupInfo.OktaSignupInfo oktaSignupInfo= new SignupInfo.OktaSignupInfo(accessToken, username);

String defaultRole = RBAC.Role.MEMBER.name();
if (UsageMetricCalculator.isRbacFeatureAvailable(accountId)) {
defaultRole = fetchDefaultInviteRole(accountId, RBAC.Role.GUEST.name());
}

shouldLogin = "true";
createUserAndRedirect(email, username, oktaSignupInfo, accountId, Config.ConfigType.OKTA.toString(), defaultRole);
createUserAndRedirectWithDefaultRole(email, username, oktaSignupInfo, accountId, Config.ConfigType.OKTA.toString());
code = "";
} catch (Exception e) {
loggerMaker.errorAndAddToDb("Error while signing in via okta sso \n" + e.getMessage(), LogDb.DASHBOARD);
Expand Down Expand Up @@ -587,15 +587,20 @@ public String fetchDefaultInviteRole(int accountId, String fallbackDefault){
public String sendRequestToSamlIdP() throws IOException{
String queryString = servletRequest.getQueryString();
String emailId = Util.getValueFromQueryString(queryString, "email");
if(emailId.isEmpty()){
if(!DashboardMode.isOnPremDeployment() && emailId.isEmpty()){
code = "Error, user email cannot be empty";
logger.error(code);
servletResponse.sendRedirect("/login");
return ERROR.toUpperCase();
}
logger.info("Trying to sign in for: " + emailId);
setUserEmail(emailId);
SAMLConfig samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail);
SAMLConfig samlConfig = null;
if(userEmail != null && !userEmail.isEmpty()) {
samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail);
} else if(!DashboardMode.isOnPremDeployment()) {
samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, ConfigType.AZURE);
}
if(samlConfig == null) {
code = "Error, cannot login via SSO, trying to login with okta sso";
logger.error(code);
Expand Down Expand Up @@ -681,12 +686,7 @@ public String registerViaAzure() throws Exception{
logger.info("Successful signing with Azure Idp for: "+ useremail);
SignupInfo.SamlSsoSignupInfo signUpInfo = new SignupInfo.SamlSsoSignupInfo(username, useremail, Config.ConfigType.AZURE);

String defaultRole = RBAC.Role.MEMBER.name();
if (UsageMetricCalculator.isRbacFeatureAvailable(this.accountId)) {
defaultRole = fetchDefaultInviteRole(this.accountId,RBAC.Role.GUEST.name());
}

createUserAndRedirect(useremail, username, signUpInfo, this.accountId, Config.ConfigType.AZURE.toString(), defaultRole);
createUserAndRedirectWithDefaultRole(useremail, username, signUpInfo, this.accountId, Config.ConfigType.AZURE.toString());
} catch (Exception e1) {
loggerMaker.errorAndAddToDb("Error while signing in via azure sso \n" + e1.getMessage(), LogDb.DASHBOARD);
servletResponse.sendRedirect("/login");
Expand Down Expand Up @@ -736,12 +736,7 @@ public String registerViaGoogleSamlSso() throws IOException{
shouldLogin = "true";
SignupInfo.SamlSsoSignupInfo signUpInfo = new SignupInfo.SamlSsoSignupInfo(username, userEmail, Config.ConfigType.GOOGLE_SAML);

String defaultRole = RBAC.Role.MEMBER.name();
if (UsageMetricCalculator.isRbacFeatureAvailable(this.accountId)) {
defaultRole = fetchDefaultInviteRole(this.accountId, RBAC.Role.GUEST.name());
}

createUserAndRedirect(userEmail, username, signUpInfo, this.accountId, Config.ConfigType.GOOGLE_SAML.toString(), defaultRole);
createUserAndRedirectWithDefaultRole(userEmail, username, signUpInfo, this.accountId, Config.ConfigType.GOOGLE_SAML.toString());
} catch (Exception e1) {
loggerMaker.errorAndAddToDb("Error while signing in via google workspace sso \n" + e1.getMessage(), LogDb.DASHBOARD);
servletResponse.sendRedirect("/login");
Expand Down Expand Up @@ -828,6 +823,15 @@ private void createUserAndRedirect(String userEmail, String username, SignupInfo
createUserAndRedirect(userEmail, username, signupInfo, invitationToAccount, method, null);
}

private void createUserAndRedirectWithDefaultRole(String userEmail, String username, SignupInfo signupInfo,
int invitationToAccount, String method) throws IOException {
String defaultRole = RBAC.Role.MEMBER.name();
if (UsageMetricCalculator.isRbacFeatureAvailable(invitationToAccount)) {
defaultRole = fetchDefaultInviteRole(invitationToAccount, RBAC.Role.GUEST.name());
}
createUserAndRedirect(userEmail, username, signupInfo, invitationToAccount, method, defaultRole);
}

private void createUserAndRedirect(String userEmail, String username, SignupInfo signupInfo,
int invitationToAccount, String method, String invitedRole) throws IOException {
loggerMaker.infoAndAddToDb("createUserAndRedirect called");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private SAMLConfig getConfig(ConfigType configType, String domain){
public String addSamlSsoInfo(){
String userLogin = getSUser().getLogin();
String domain = userLogin.split("@")[1];
if (SsoUtils.isAnySsoActive(Context.accountId.get())) {
if (SsoUtils.isAnySsoActive()) {
addActionError("A SSO Integration already exists.");
return ERROR.toUpperCase();
}
Expand Down Expand Up @@ -79,7 +79,7 @@ public String execute() throws Exception {
Filters.eq("configType", configType.name())
)
);
if (SsoUtils.isAnySsoActive(Context.accountId.get()) && samlConfig == null) {
if (SsoUtils.isAnySsoActive() && samlConfig == null) {
addActionError("A different SSO Integration already exists.");
return ERROR.toUpperCase();
}
Expand Down
38 changes: 38 additions & 0 deletions apps/dashboard/src/main/java/com/akto/utils/GithubLogin.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
import com.akto.dao.context.Context;
import com.akto.dto.Config;
import com.akto.dto.Config.GithubConfig;
import com.akto.dto.OriginalHttpRequest;
import com.akto.dto.OriginalHttpResponse;
import com.akto.testing.ApiExecutor;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.*;

public class GithubLogin {

public static final int PROBE_PERIOD_IN_SECS = 60;
private static GithubLogin instance = null;
private GithubConfig githubConfig = null;
private int lastProbeTs = 0;
public static final String GET_GITHUB_EMAILS_URL = "https://api.github.com/user/emails";

public static GithubLogin getInstance() {
boolean shouldProbeAgain = true;
Expand Down Expand Up @@ -52,6 +60,36 @@ public static String getGithubUrl() {
return githubUrl;
}

public static List<Map<String, String>> getEmailRequest(String accessToken){
ObjectMapper objectMapper = new ObjectMapper();
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", Collections.singletonList("application/vnd.github+json"));
headers.put("Authorization", Collections.singletonList("Bearer " + accessToken));
headers.put("X-GitHub-Api-Version", Collections.singletonList("2022-11-28"));

OriginalHttpRequest request = new OriginalHttpRequest(GET_GITHUB_EMAILS_URL, "", "GET", null, headers, "");
OriginalHttpResponse response = null;
try {
response = ApiExecutor.sendRequest(request, false, null, false, new ArrayList<>());
return objectMapper.readValue(response.getBody(), new TypeReference<List<Map<String, String>>>() {});
}catch(Exception e){
return null;
}
}

public static String getPrimaryGithubEmail(List<Map<String, String>> emailResp){
if(emailResp == null){
return "";
}else{
for (Map<String, String> entryMap : emailResp) {
if(entryMap.get("primary").equals("true")){
return entryMap.get("email");
}
}
}
return null;
}

private GithubLogin() {
}

Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static OktaLogin getInstance() {
}

if (shouldProbeAgain) {
OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(Context.accountId.get()));
OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(1000000));
if (instance == null) {
instance = new OktaLogin();
}
Expand All @@ -47,7 +47,7 @@ public static String getAuthorisationUrl() {
paramMap.put("redirect_uri",oktaConfig.getRedirectUri());
paramMap.put("response_type", "code");
paramMap.put("scope", "openid%20email%20profile");
paramMap.put("state", "login");
paramMap.put("state", String.valueOf(oktaConfig.getAccountId()));

String queryString = SsoUtils.getQueryString(paramMap);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static boolean isAnySsoActive(){
}else{
List<String> ssoList = Arrays.asList(oktaIdString, "GITHUB-ankush", "AZURE-ankush");
Bson filter = Filters.in("_id", ssoList);
return ConfigsDao.instance.count(filter) > 0;
return ConfigsDao.instance.count(filter) > 0 || isAnySsoActive(1000000);
}
}

Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/web/pages/login.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@
window.TIME_ZONE = '${requestScope.currentTimeZone}'
window.USER_FULL_NAME = '${requestScope.userFullName}'
window.ORGANIZATION_NAME = '${requestScope.organizationName}'
window.GOOGLE_SSO_URL=atob('${requestScope.googleSsoUrl}')
window.GOOGLE_SAML_AUTH_URL=atob('${requestScope.googleSamlAuthUrl}')
window.OKTA_AUTH_URL = '${requestScope.oktaAuthUrl}'
window.AZURE_AUTH_URL = '${requestScope.azureAuthUrl}'
window.GITHUB_AUTH_URL = '${requestScope.githubAuthUrl}'
window.ACTIVE_SSO = '${requestScope.activeSso}'

window.STIGG_IS_OVERAGE='${requestScope.stiggIsOverage}'
window.USAGE_PAUSED=JSON.parse('${requestScope.usagePaused}' || '{}');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function GithubSso() {
const [showGithubSsoModal, setShowGithubSsoModal] = useState(false)
const [githubPresent, setGithubPresent] = useState("")
const [componentType, setComponentType] = useState(0) ;
const [nextButtonActive,setNextButtonActive] = useState(window.DASHBOARD_MODE === "ON_PREM");
const [nextButtonActive,setNextButtonActive] = useState();
const [githubUrl, setGithubUrl] = useState("https://github.com")
const [githubApiUrl, setGithubApiUrl] = useState("https://api.github.com")

Expand Down Expand Up @@ -61,6 +61,7 @@ function GithubSso() {
setGithubClientId(githubClientId)
if (githubUrl) setGithubUrl(githubUrl)
if (githubApiUrl) setGithubApiUrl(githubApiUrl)
setNextButtonActive(true)
} catch (error) {
setNextButtonActive(false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function AzureSso() {

const [loginUrl, setLoginUrl] = useState('')
const [azureIdentity, setAzureIdentity] = useState('')
const [nextButtonActive, setNextButtonActive] = useState()


const cardContent = "Enable Login via Azure AD on your Akto dashboard";
Expand Down Expand Up @@ -62,10 +63,12 @@ function AzureSso() {
await settingRequests.fetchAzureSso("AZURE").then((resp)=> {
setLoginUrl(resp.loginUrl)
setAzureIdentity(resp.ssoEntityId)
setNextButtonActive(true)
})
setLoading(false)
} catch (error) {
setLoading(false)
setNextButtonActive(false)
}
}

Expand All @@ -92,6 +95,7 @@ function AzureSso() {
pageTitle={"Azure AD SSO SAML"}
loading={loading}
certificateName={"Federation Metadata XML"}
isButtonActive={nextButtonActive}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import func from "@/util/func"
import Details from '../components/Details';
import { CancelMajor } from "@shopify/polaris-icons"

function CustomSamlSso({ssoType,entityTitle, entityId, loginURL,pageTitle, signinUrl, integrationSteps, cardContent, handleSubmitOutSide, handleDeleteOutside, samlUrlDocs, loading, showCustomInputs, certificateName}) {
function CustomSamlSso({ssoType,entityTitle, entityId, loginURL,pageTitle, signinUrl, integrationSteps, cardContent, handleSubmitOutSide, handleDeleteOutside, samlUrlDocs, loading, showCustomInputs, certificateName, isButtonActive}) {
const [componentType, setComponentType] = useState(0) ;
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [files, setFiles] = useState(null)
const [ssoUrl, setSSOUrl] = useState('')
const [identifier, setIdentifier] = useState('')

const stepsComponent = (
<StepsComponent integrationSteps={integrationSteps} onClickFunc={() => setComponentType(1)} buttonActive={true}/>
<StepsComponent integrationSteps={integrationSteps} onClickFunc={() => setComponentType(1)} buttonActive={isButtonActive}/>
)

const setFilesCheck = (file) => {
Expand Down
Loading
Loading