From 57919bebc17de59bc18ffd4692fff44c770b4896 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 19 May 2025 15:03:27 +0530 Subject: [PATCH 01/10] Support ApiServer to enforce POST requests with timestamps for state changing APIs Co-authored-by: Kevin Li --- .../apache/cloudstack/api/ApiErrorCode.java | 1 + .../cloudstack/api/ApiServerService.java | 2 ++ .../main/java/com/cloud/api/ApiServer.java | 26 ++++++++++++++++- .../main/java/com/cloud/api/ApiServlet.java | 28 +++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java b/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java index 03dc37325d4b..616c37484d87 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java @@ -22,6 +22,7 @@ */ public enum ApiErrorCode { + BAD_REQUEST(400), UNAUTHORIZED(401), UNAUTHORIZED2FA(511), METHOD_NOT_ALLOWED(405), diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index cbbcdc3bda42..29cf96619ba9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -48,4 +48,6 @@ public ResponseObject loginUser(HttpSession session, String username, String pas boolean forgotPassword(UserAccount userAccount, Domain domain); boolean resetPassword(UserAccount userAccount, String token, String password); + + boolean isEnforcePostRequestsAndTimestamps(); } diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index b8227ef9d589..52dce41eba78 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -201,6 +201,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer private static final String SANITIZATION_REGEX = "[\n\r]"; private static boolean encodeApiResponse = false; + private boolean isEnforcePostRequestsAndTimestamps = false; /** * Non-printable ASCII characters - numbers 0 to 31 and 127 decimal @@ -284,6 +285,13 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer , "Session cookie is marked as secure if this is enabled. Secure cookies only work when HTTPS is used." , false , ConfigKey.Scope.Global); + static final ConfigKey EnforcePostRequestsAndTimestamps = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED + , Boolean.class + , "enable.enforce.post.requests.and.timestamps" + , "false" + , "enables/disables whether the ApiServer only accepts state-changing POST requests and requests with timestamps." + , false + , ConfigKey.Scope.Global); private static final ConfigKey JSONDefaultContentType = new ConfigKey<> (ConfigKey.CATEGORY_ADVANCED , String.class , "json.content.type" @@ -441,6 +449,7 @@ protected void setupIntegrationPortListener(Integer apiPort) { public boolean start() { Security.addProvider(new BouncyCastleProvider()); Integer apiPort = IntegrationAPIPort.value(); // api port, null by default + isEnforcePostRequestsAndTimestamps = EnforcePostRequestsAndTimestamps.value(); final Long snapshotLimit = ConcurrentSnapshotsThresholdPerHost.value(); if (snapshotLimit == null || snapshotLimit <= 0) { @@ -720,6 +729,11 @@ public String handleRequest(final Map params, final String responseType, final S return response; } + @Override + public boolean isEnforcePostRequestsAndTimestamps() { + return isEnforcePostRequestsAndTimestamps; + } + private String getBaseAsyncResponse(final long jobId, final BaseAsyncCmd cmd) { final AsyncJobResponse response = new AsyncJobResponse(); @@ -967,7 +981,6 @@ public boolean verifyRequest(final Map requestParameters, fina // put the name in a list that we'll sort later final List parameterNames = new ArrayList<>(requestParameters.keySet()); - Collections.sort(parameterNames); String signatureVersion = null; @@ -1019,12 +1032,22 @@ public boolean verifyRequest(final Map requestParameters, fina } final Date now = new Date(System.currentTimeMillis()); + final Date thresholdTime = new Date(now.getTime() + 15 * 60 * 1000); if (expiresTS.before(now)) { signature = signature.replaceAll(SANITIZATION_REGEX, "_"); apiKey = apiKey.replaceAll(SANITIZATION_REGEX, "_"); logger.debug("Request expired -- ignoring ...sig [{}], apiKey [{}].", signature, apiKey); return false; + } else if (isEnforcePostRequestsAndTimestamps && expiresTS.after(thresholdTime)) { + signature = signature.replaceAll(SANITIZATION_REGEX, "_"); + apiKey = apiKey.replaceAll(SANITIZATION_REGEX, "_"); + logger.debug(String.format("Expiration parameter is set for too long -- ignoring ...sig [%s], apiKey [%s].", signature, apiKey)); + return false; } + } else if (isEnforcePostRequestsAndTimestamps) { + // Force expiration parameter + logger.debug("Signature Version must be 3, and should be along with the Expires parameter -- ignoring request."); + return false; } final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB); @@ -1648,6 +1671,7 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] { + EnforcePostRequestsAndTimestamps, IntegrationAPIPort, ConcurrentSnapshotsThresholdPerHost, EncodeApiResponse, diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index 4994c42bb4dc..1308e7ebce89 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -22,8 +22,11 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; +import java.util.Set; import javax.inject.Inject; import javax.servlet.ServletConfig; @@ -78,6 +81,9 @@ public class ApiServlet extends HttpServlet { private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServlet.class.getName()); private static final String REPLACEMENT = "_"; private static final String LOGGER_REPLACEMENTS = "[\n\r\t]"; + private static final Pattern GET_REQUEST_COMMANDS = Pattern.compile("^(get|list|query|find)(\\w+)+$"); + private static final HashSet GET_REQUEST_COMMANDS_LIST = new HashSet(Set.of("isaccountallowedtocreateofferingswithtags", + "readyforshutdown", "cloudianisenabled", "quotabalance", "quotasummary", "quotatarifflist", "quotaisenabled", "quotastatement")); @Inject ApiServerService apiServer; @@ -317,6 +323,19 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp } } + if (apiServer.isEnforcePostRequestsAndTimestamps() && !isStateChangingCommandUsingPOST(command, req.getMethod(), params)) { + String errorText = String.format("State changing command %s needs to be sent using POST request", command); + if (command.equalsIgnoreCase("updateConfiguration") && params.containsKey("name")) { + errorText = String.format("Changes for configuration %s needs to be sent using POST request", params.get("name")[0]); + } + auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST + " " + errorText); + final String serializedResponse = + apiServer.getSerializedApiError(new ServerApiException(ApiErrorCode.BAD_REQUEST, errorText), params, + responseType); + HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_BAD_REQUEST, responseType, ApiServer.JSONcontentType.value()); + return; + } + Long userId = null; if (!isNew) { userId = (Long)session.getAttribute("userid"); @@ -407,6 +426,15 @@ private boolean checkIfAuthenticatorIsOf2FA(String command) { return verify2FA; } + private boolean isStateChangingCommandUsingPOST(String command, String method, Map params) { + if (command == null || (!GET_REQUEST_COMMANDS.matcher(command.toLowerCase()).matches() && !GET_REQUEST_COMMANDS_LIST.contains(command.toLowerCase()) + && !command.equalsIgnoreCase("updateConfiguration") && !method.equals("POST"))) { + return false; + } + return !command.equalsIgnoreCase("updateConfiguration") || method.equals("POST") || (params.containsKey("name") + && params.get("name")[0].toString().equalsIgnoreCase(ApiServer.EnforcePostRequestsAndTimestamps.key())); + } + protected boolean skip2FAcheckForAPIs(String command) { boolean skip2FAcheck = false; From d233aa60a9b8a5580adde3630b6ffa13f719e40b Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Wed, 21 May 2025 12:37:19 +0530 Subject: [PATCH 02/10] UI changes to use POST requests for state changing APIs Co-authored-by: Kevin Li --- ui/src/api/index.js | 44 ++- .../components/header/SamlDomainSwitcher.vue | 6 +- ui/src/components/header/UserMenu.vue | 4 +- ui/src/components/page/GlobalLayout.vue | 4 +- ui/src/components/view/ActionButton.vue | 4 +- ui/src/components/view/AnnotationsTab.vue | 10 +- ui/src/components/view/DedicateData.vue | 18 +- ui/src/components/view/DedicateDomain.vue | 6 +- ui/src/components/view/DedicateModal.vue | 10 +- ui/src/components/view/DetailSettings.vue | 8 +- ui/src/components/view/EventsTab.vue | 4 +- ui/src/components/view/InfoCard.vue | 14 +- ...stanceVolumesStoragePoolSelectListView.vue | 4 +- ui/src/components/view/ListResourceTable.vue | 4 +- ui/src/components/view/ListView.vue | 12 +- .../components/view/NicNetworkSelectForm.vue | 4 +- ui/src/components/view/ResourceLimitTab.vue | 6 +- ui/src/components/view/ResourceView.vue | 4 +- ui/src/components/view/SearchView.vue | 36 +-- ui/src/components/view/SettingsTab.vue | 4 +- ui/src/components/view/StatsTab.vue | 4 +- .../components/view/StoragePoolSelectView.vue | 6 +- ui/src/components/view/TreeView.vue | 8 +- ui/src/components/view/UploadResourceIcon.vue | 6 +- ui/src/components/view/VmwareData.vue | 4 +- ui/src/components/view/VolumesTab.vue | 4 +- ui/src/components/widgets/Console.vue | 4 +- ui/src/components/widgets/OsLogo.vue | 4 +- ui/src/store/modules/user.js | 30 +- ui/src/utils/plugins.js | 4 +- ui/src/views/AutogenView.vue | 21 +- ui/src/views/auth/Login.vue | 8 +- ui/src/views/compute/AssignInstance.vue | 6 +- ui/src/views/compute/AttachIso.vue | 6 +- .../views/compute/AutoScaleDownPolicyTab.vue | 26 +- .../views/compute/AutoScaleLoadBalancing.vue | 26 +- ui/src/views/compute/AutoScaleUpPolicyTab.vue | 26 +- ui/src/views/compute/AutoScaleVmProfile.vue | 14 +- ui/src/views/compute/BackupScheduleWizard.vue | 4 +- ui/src/views/compute/ChangeAffinity.vue | 6 +- .../views/compute/CreateAutoScaleVmGroup.vue | 30 +- .../views/compute/CreateKubernetesCluster.vue | 14 +- ui/src/views/compute/CreateSSHKeyPair.vue | 8 +- ui/src/views/compute/CreateSnapshotWizard.vue | 6 +- ui/src/views/compute/DeployVM.vue | 20 +- ui/src/views/compute/DestroyVM.vue | 6 +- ui/src/views/compute/EditVM.vue | 22 +- ui/src/views/compute/InstanceTab.vue | 8 +- ui/src/views/compute/KubernetesServiceTab.vue | 14 +- ui/src/views/compute/MigrateVMStorage.vue | 4 +- ui/src/views/compute/MigrateWizard.vue | 8 +- ui/src/views/compute/RegisterUserData.vue | 6 +- ui/src/views/compute/ReinstallVm.vue | 8 +- ui/src/views/compute/ResetSshKeyPair.vue | 6 +- ui/src/views/compute/ResetUserData.vue | 15 +- .../views/compute/ScaleKubernetesCluster.vue | 10 +- ui/src/views/compute/ScaleVM.vue | 10 +- ui/src/views/compute/StartVirtualMachine.vue | 10 +- .../compute/UpgradeKubernetesCluster.vue | 6 +- .../views/compute/backup/BackupSchedule.vue | 4 +- ui/src/views/compute/backup/FormSchedule.vue | 4 +- .../compute/wizard/MultiDiskSelection.vue | 4 +- .../compute/wizard/MultiNetworkSelection.vue | 4 +- .../views/compute/wizard/NetworkSelection.vue | 10 +- .../compute/wizard/SecurityGroupSelection.vue | 4 +- ui/src/views/dashboard/CapacityDashboard.vue | 26 +- ui/src/views/dashboard/Dashboard.vue | 4 +- ui/src/views/dashboard/SetupTwoFaAtLogin.vue | 12 +- ui/src/views/dashboard/UsageDashboard.vue | 28 +- ui/src/views/dashboard/VerifyTwoFa.vue | 4 +- ui/src/views/iam/AddAccount.vue | 12 +- ui/src/views/iam/AddLdapAccount.vue | 16 +- ui/src/views/iam/AddUser.vue | 12 +- ui/src/views/iam/ChangeUserPassword.vue | 4 +- ui/src/views/iam/ConfigureSamlSsoAuth.vue | 8 +- ui/src/views/iam/CreateRole.vue | 6 +- ui/src/views/iam/DomainActionForm.vue | 4 +- ui/src/views/iam/DomainView.vue | 31 +- ui/src/views/iam/EditUser.vue | 4 +- ui/src/views/iam/ImportRole.vue | 4 +- ui/src/views/iam/RolePermissionTab.vue | 12 +- ui/src/views/iam/SSLCertificateTab.vue | 6 +- ui/src/views/iam/SetupTwoFaAtUserProfile.vue | 12 +- .../image/AddKubernetesSupportedVersion.vue | 6 +- ui/src/views/image/IsoZones.vue | 10 +- ui/src/views/image/RegisterOrUploadIso.vue | 18 +- .../views/image/RegisterOrUploadTemplate.vue | 24 +- ui/src/views/image/TemplateZones.vue | 10 +- ui/src/views/image/UpdateISO.vue | 10 +- .../UpdateKubernetesSupportedVersion.vue | 4 +- ui/src/views/image/UpdateTemplate.vue | 10 +- .../image/UpdateTemplateIsoPermissions.vue | 12 +- ui/src/views/infra/AddPrimaryStorage.vue | 16 +- ui/src/views/infra/AddSecondaryStorage.vue | 8 +- ui/src/views/infra/AsyncJobsTab.vue | 4 +- ui/src/views/infra/ClusterAdd.vue | 14 +- ui/src/views/infra/Confirmation.vue | 4 +- ui/src/views/infra/CpuSockets.vue | 4 +- ui/src/views/infra/HostAdd.vue | 14 +- ui/src/views/infra/HostEnableDisable.vue | 6 +- ui/src/views/infra/HostInfo.vue | 4 +- ui/src/views/infra/HostUpdate.vue | 6 +- ui/src/views/infra/InfraSummary.vue | 12 +- .../views/infra/ManagementServerPeerTab.vue | 4 +- ui/src/views/infra/Metrics.vue | 6 +- ui/src/views/infra/MigrateData.vue | 6 +- ui/src/views/infra/PodAdd.vue | 8 +- ui/src/views/infra/Resources.vue | 4 +- .../views/infra/network/DedicatedVLANTab.vue | 14 +- .../views/infra/network/EditTrafficLabel.vue | 6 +- .../views/infra/network/IpRangesTabGuest.vue | 10 +- .../infra/network/IpRangesTabManagement.vue | 8 +- .../views/infra/network/IpRangesTabPublic.vue | 18 +- .../infra/network/IpRangesTabStorage.vue | 10 +- .../infra/network/ServiceProvidersTab.vue | 12 +- .../views/infra/network/TrafficTypesTab.vue | 12 +- .../network/providers/AddF5LoadBalancer.vue | 6 +- .../providers/AddNetscalerLoadBalancer.vue | 6 +- .../network/providers/AddNiciraNvpDevice.vue | 6 +- .../network/providers/AddPaloAltoFirewall.vue | 6 +- .../infra/network/providers/ProviderItem.vue | 4 +- .../network/providers/ProviderListView.vue | 6 +- .../views/infra/routers/RouterHealthCheck.vue | 6 +- .../views/infra/zone/PhysicalNetworksTab.vue | 8 +- ui/src/views/infra/zone/SystemVmsTab.vue | 4 +- .../infra/zone/ZoneWizardAddResources.vue | 10 +- .../views/infra/zone/ZoneWizardLaunchZone.vue | 68 ++--- .../infra/zone/ZoneWizardNetworkSetupStep.vue | 4 +- .../infra/zone/ZoneWizardZoneDetailsStep.vue | 8 +- ui/src/views/network/AclListRulesTab.vue | 22 +- .../network/CreateIsolatedNetworkForm.vue | 14 +- ui/src/views/network/CreateL2NetworkForm.vue | 10 +- ui/src/views/network/CreateNetwork.vue | 4 +- .../views/network/CreateNetworkPermission.vue | 4 +- .../views/network/CreateSharedNetworkForm.vue | 22 +- ui/src/views/network/CreateVlanIpRange.vue | 8 +- ui/src/views/network/CreateVpc.vue | 12 +- .../network/CreateVpnCustomerGateway.vue | 4 +- ui/src/views/network/EgressRulesTab.vue | 12 +- ui/src/views/network/EnableStaticNat.vue | 12 +- ui/src/views/network/FirewallRules.vue | 18 +- ui/src/views/network/GuestIpRanges.vue | 8 +- ui/src/views/network/GuestVlanNetworksTab.vue | 4 +- .../network/IngressEgressRuleConfigure.vue | 16 +- .../views/network/InternalLBAssignVmForm.vue | 10 +- .../views/network/InternalLBAssignedVmTab.vue | 6 +- ui/src/views/network/IpAddressesTab.vue | 14 +- ui/src/views/network/Ipv6FirewallRulesTab.vue | 8 +- ui/src/views/network/LoadBalancing.vue | 42 +-- ui/src/views/network/NetworkPermissions.vue | 8 +- ui/src/views/network/NicsTable.vue | 4 +- ui/src/views/network/PortForwarding.vue | 20 +- ui/src/views/network/PublicIpResource.vue | 8 +- ui/src/views/network/ReservePublicIP.vue | 10 +- ui/src/views/network/RoutersTab.vue | 4 +- ui/src/views/network/StaticRoutesTab.vue | 14 +- ui/src/views/network/UpdateNetwork.vue | 8 +- ui/src/views/network/VpcTab.vue | 26 +- ui/src/views/network/VpcTiersTab.vue | 26 +- ui/src/views/network/VpnDetails.vue | 8 +- .../network/tungsten/FirewallPolicyTab.vue | 8 +- .../network/tungsten/FirewallRuleTab.vue | 18 +- .../views/network/tungsten/FirewallTagTab.vue | 10 +- .../network/tungsten/LogicalRouterTab.vue | 8 +- .../network/tungsten/NetworkPolicyTab.vue | 10 +- .../tungsten/TungstenFabricPolicyRule.vue | 8 +- .../tungsten/TungstenFabricPolicyTag.vue | 10 +- .../tungsten/TungstenFabricTableView.vue | 10 +- ui/src/views/offering/AddComputeOffering.vue | 18 +- ui/src/views/offering/AddDiskOffering.vue | 14 +- ui/src/views/offering/AddNetworkOffering.vue | 18 +- ui/src/views/offering/AddVpcOffering.vue | 14 +- .../views/offering/ImportBackupOffering.vue | 8 +- .../views/offering/UpdateOfferingAccess.vue | 10 +- ui/src/views/plugins/CloudianPlugin.vue | 4 +- .../plugins/quota/EditTariffValueWizard.vue | 4 +- .../plugins/quota/EmailTemplateDetails.vue | 6 +- ui/src/views/plugins/quota/QuotaBalance.vue | 6 +- .../plugins/quota/QuotaSummaryResource.vue | 4 +- ui/src/views/plugins/quota/QuotaUsage.vue | 6 +- ui/src/views/project/AccountsTab.vue | 12 +- .../project/AddAccountOrUserToProject.vue | 12 +- .../views/project/InvitationTokenTemplate.vue | 4 +- ui/src/views/project/InvitationsTemplate.vue | 6 +- .../project/iam/ProjectRolePermissionTab.vue | 12 +- ui/src/views/project/iam/ProjectRoleTab.vue | 10 +- ui/src/views/setting/ConfigurationTab.vue | 6 +- ui/src/views/setting/ConfigurationValue.vue | 6 +- ui/src/views/storage/AttachVolume.vue | 6 +- .../views/storage/ChangeOfferingForVolume.vue | 6 +- .../storage/CreateSnapshotFromVMSnapshot.vue | 6 +- ui/src/views/storage/CreateVolume.vue | 14 +- ui/src/views/storage/FormSchedule.vue | 6 +- ui/src/views/storage/MigrateVolume.vue | 10 +- .../views/storage/RecurringSnapshotVolume.vue | 4 +- ui/src/views/storage/ResizeVolume.vue | 6 +- .../storage/RestoreAttachBackupVolume.vue | 6 +- ui/src/views/storage/ScheduledSnapshots.vue | 4 +- ui/src/views/storage/TakeSnapshot.vue | 6 +- ui/src/views/storage/UploadLocalVolume.vue | 12 +- ui/src/views/storage/UploadVolume.vue | 12 +- .../views/tools/ImportUnmanagedInstance.vue | 16 +- ui/src/views/tools/ManageInstances.vue | 14 +- ui/tests/common/index.js | 10 + .../unit/components/view/ActionButton.spec.js | 30 +- ui/tests/unit/views/AutogenView.spec.js | 286 ++++++++---------- .../unit/views/compute/MigrateWizard.spec.js | 38 +-- 207 files changed, 1221 insertions(+), 1244 deletions(-) diff --git a/ui/src/api/index.js b/ui/src/api/index.js index 7ab87780a9d4..85c46c483b24 100644 --- a/ui/src/api/index.js +++ b/ui/src/api/index.js @@ -23,18 +23,10 @@ import { ACCESS_TOKEN } from '@/store/mutation-types' -export function api (command, args = {}, method = 'GET', data = {}) { - let params = {} +export function getAPI (command, args = {}) { args.command = command args.response = 'json' - if (data) { - params = new URLSearchParams() - Object.entries(data).forEach(([key, value]) => { - params.append(key, value) - }) - } - const sessionkey = vueProps.$localStorage.get(ACCESS_TOKEN) || Cookies.get('sessionkey') if (sessionkey) { args.sessionkey = sessionkey @@ -45,8 +37,30 @@ export function api (command, args = {}, method = 'GET', data = {}) { ...args }, url: '/', - method, - data: params || {} + method: 'GET' + }) +} + +export function postAPI (command, data = {}) { + const params = new URLSearchParams() + params.append('command', command) + params.append('response', 'json') + if (data) { + Object.entries(data).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + params.append(key, value) + } + }) + } + + const sessionkey = vueProps.$localStorage.get(ACCESS_TOKEN) || Cookies.get('sessionkey') + if (sessionkey) { + params.append('sessionkey', sessionkey) + } + return axios({ + url: '/', + method: 'POST', + data: params }) } @@ -56,7 +70,7 @@ export function login (arg) { } // Logout before login is called to purge any duplicate sessionkey cookies - api('logout') + postAPI('logout') const params = new URLSearchParams() params.append('command', 'login') @@ -66,7 +80,7 @@ export function login (arg) { params.append('response', 'json') return axios({ url: '/', - method: 'post', + method: 'POST', data: params, headers: { 'content-type': 'application/x-www-form-urlencoded' @@ -77,7 +91,7 @@ export function login (arg) { export function logout () { message.destroy() notification.destroy() - return api('logout') + return postAPI('logout') } export function oauthlogin (arg) { @@ -86,7 +100,7 @@ export function oauthlogin (arg) { } // Logout before login is called to purge any duplicate sessionkey cookies - api('logout') + postAPI('logout') const params = new URLSearchParams() params.append('command', 'oauthlogin') diff --git a/ui/src/components/header/SamlDomainSwitcher.vue b/ui/src/components/header/SamlDomainSwitcher.vue index 082bab7bf13c..128a94f0f72d 100644 --- a/ui/src/components/header/SamlDomainSwitcher.vue +++ b/ui/src/components/header/SamlDomainSwitcher.vue @@ -51,7 +51,7 @@