| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| package com.cloud.api.auth; | |
| import com.cloud.api.ApiServlet; | |
| import com.cloud.domain.Domain; | |
| import com.cloud.user.User; | |
| import com.cloud.user.UserAccount; | |
| import com.cloud.utils.StringUtils; | |
| import org.apache.cloudstack.api.ApiServerService; | |
| import com.cloud.api.response.ApiResponseSerializer; | |
| import com.cloud.exception.CloudAuthenticationException; | |
| import com.cloud.user.Account; | |
| import org.apache.cloudstack.api.APICommand; | |
| import org.apache.cloudstack.api.ApiConstants; | |
| import org.apache.cloudstack.api.ApiErrorCode; | |
| import org.apache.cloudstack.api.BaseCmd; | |
| import org.apache.cloudstack.api.Parameter; | |
| import org.apache.cloudstack.api.ServerApiException; | |
| import org.apache.cloudstack.api.auth.APIAuthenticationType; | |
| import org.apache.cloudstack.api.auth.APIAuthenticator; | |
| import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; | |
| import org.apache.cloudstack.api.response.LoginCmdResponse; | |
| import org.jetbrains.annotations.Nullable; | |
| import javax.inject.Inject; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import javax.servlet.http.HttpSession; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.net.InetAddress; | |
| @APICommand(name = "login", description = "Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {}) | |
| public class DefaultLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator { | |
| ///////////////////////////////////////////////////// | |
| //////////////// API parameters ///////////////////// | |
| ///////////////////////////////////////////////////// | |
| @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "Username", required = true) | |
| private String username; | |
| @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, description = "Hashed password (Default is MD5). If you wish to use any other hashing algorithm, you would need to write a custom authentication adapter See Docs section.", required = true) | |
| private String password; | |
| @Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, description = "Path of the domain that the user belongs to. Example: domain=/com/cloud/internal. If no domain is passed in, the ROOT (/) domain is assumed.") | |
| private String domain; | |
| @Parameter(name = ApiConstants.DOMAIN__ID, type = CommandType.LONG, description = "The id of the domain that the user belongs to. If both domain and domainId are passed in, \"domainId\" parameter takes precedence.") | |
| private Long domainId; | |
| @Inject | |
| ApiServerService _apiServer; | |
| ///////////////////////////////////////////////////// | |
| /////////////////// Accessors /////////////////////// | |
| ///////////////////////////////////////////////////// | |
| public String getUsername() { | |
| return username; | |
| } | |
| public String getPassword() { | |
| return password; | |
| } | |
| public String getDomainName() { | |
| return domain; | |
| } | |
| public Long getDomainId() { | |
| return domainId; | |
| } | |
| ///////////////////////////////////////////////////// | |
| /////////////// API Implementation/////////////////// | |
| ///////////////////////////////////////////////////// | |
| @Override | |
| public long getEntityOwnerId() { | |
| return Account.Type.NORMAL.ordinal(); | |
| } | |
| @Override | |
| public void execute() throws ServerApiException { | |
| // We should never reach here | |
| throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly"); | |
| } | |
| @Override | |
| public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { | |
| // Disallow non POST requests | |
| if (HTTPMethod.valueOf(req.getMethod()) != HTTPMethod.POST) { | |
| throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "Please use HTTP POST to authenticate using this API"); | |
| } | |
| // FIXME: ported from ApiServlet, refactor and cleanup | |
| final String[] username = (String[])params.get(ApiConstants.USERNAME); | |
| final String[] password = (String[])params.get(ApiConstants.PASSWORD); | |
| String domainIdStr = _apiServer.getDomainId(params); | |
| Long domainId = null; | |
| if (StringUtils.isNotEmpty(domainIdStr)) { | |
| try { | |
| //check if UUID is passed in for domain | |
| domainId = _apiServer.fetchDomainId(domainIdStr); | |
| if (domainId == null) { | |
| domainId = Long.parseLong(domainIdStr); | |
| } | |
| auditTrailSb.append(" domainid=" + domainId);// building the params for POST call | |
| } catch (final NumberFormatException e) { | |
| logger.warn("Invalid domain id entered by user"); | |
| auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one"); | |
| throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, | |
| _apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params, | |
| responseType)); | |
| } | |
| } | |
| String domain = null; | |
| final String[] domainName = (String[])params.get(ApiConstants.DOMAIN); | |
| domain = getDomainName(auditTrailSb, domainName, domain); | |
| String serializedResponse = null; | |
| if (username != null) { | |
| final String pwd = ((password == null) ? null : password[0]); | |
| try { | |
| final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain); | |
| if (userDomain != null) { | |
| domainId = userDomain.getId(); | |
| } else { | |
| throw new CloudAuthenticationException("Unable to find the domain from the path " + domain); | |
| } | |
| final UserAccount userAccount = _accountService.getActiveUserAccount(username[0], domainId); | |
| if (userAccount != null && User.Source.SAML2 == userAccount.getSource()) { | |
| throw new CloudAuthenticationException("User is not allowed CloudStack login"); | |
| } | |
| return ApiResponseSerializer.toSerializedString(_apiServer.loginUser(session, username[0], pwd, domainId, domain, remoteAddress, params), | |
| responseType); | |
| } catch (final CloudAuthenticationException ex) { | |
| ApiServlet.invalidateHttpSession(session, "fall through to API key,"); | |
| // TODO: fall through to API key, or just fail here w/ auth error? (HTTP 401) | |
| String msg = String.format("%s", ex.getMessage() != null ? | |
| ex.getMessage() : | |
| "failed to authenticate user, check if username/password are correct"); | |
| auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + msg); | |
| serializedResponse = _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), msg, params, responseType); | |
| if (logger.isTraceEnabled()) { | |
| logger.trace(msg); | |
| } | |
| } | |
| } | |
| // We should not reach here and if we do we throw an exception | |
| throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, serializedResponse); | |
| } | |
| @Nullable | |
| private String getDomainName(StringBuilder auditTrailSb, String[] domainName, String domain) { | |
| if (domainName != null) { | |
| domain = domainName[0]; | |
| auditTrailSb.append(" domain=" + domain); | |
| if (domain != null) { | |
| // ensure domain starts with '/' and ends with '/' | |
| if (!domain.endsWith("/")) { | |
| domain += '/'; | |
| } | |
| if (!domain.startsWith("/")) { | |
| domain = "/" + domain; | |
| } | |
| } | |
| } | |
| return domain; | |
| } | |
| @Override | |
| public APIAuthenticationType getAPIType() { | |
| return APIAuthenticationType.LOGIN_API; | |
| } | |
| @Override | |
| public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) { | |
| } | |
| } |