{{{
xProject
jdbc/xProjectDB
javax.sql.DataSource
tenantContext
com.sap.cloud.account.TenantContext
connectivityConfiguration
com.sap.core.connectivity.api.configuration.ConnectivityConfiguration
PasswordStorage
com.sap.cloud.security.password.PasswordStorage
FORM
Protected APIs
/api/v1/*
ProjectManager
ProjectMember
ProjectManager
ProjectMember
OAuth scope definition for joining a project
ListProjectsScopeFilter
com.sap.cloud.security.oauth2.OAuthAuthorizationFilter
scope
list-projects
http-method
GET
no-session
true
ListProjectsScopeFilter
/api/v1/mobile/projects
OAuth scope definition for joining a project
JoinProjectScopeFilter
com.sap.cloud.security.oauth2.OAuthAuthorizationFilter
scope
join-project
http-method
POST
no-session
true
JoinProjectScopeFilter
/api/v1/mobile/projects
OAuth scope definition for managing a project member's
timesheets
ManageTimesheetsScopeFilter
com.sap.cloud.security.oauth2.OAuthAuthorizationFilter
scope
manage-timesheets
http-method
GET POST
no-session
true
ManageTimesheetsScopeFilter
/api/v1/mobile/timesheets
TestDataServlet
com.sap.cloud.sample.xproject.servlet.TestDataServlet
TestDataServlet
/TestDataServlet
CsrfFilter
org.apache.catalina.filters.CsrfPreventionFilter
entryPoints
/index.jsp
CsrfFilter
/api/v1/web
}}}
{{{
package com.sap.cloud.sample.xproject.web;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cloud.account.TenantContext;
import com.sap.cloud.security.password.PasswordStorage;
import com.sap.cloud.security.password.PasswordStorageException;
import com.sap.core.connectivity.api.configuration.ConnectivityConfiguration;
import com.sap.core.connectivity.api.configuration.DestinationConfiguration;
public class Util {
private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
private static final String JNDI_KEY_DATA_SOURCE = "java:comp/env/jdbc/xProjectDB";
private static final String JNDI_KEY_CONNECTIVITY_CONFIG = "java:comp/env/connectivityConfiguration";
private static final String JNDI_KEY_PASSWORD_STORAGE = "java:comp/env/PasswordStorage";
private static final String DESTINATION_OAUTHAS_TOKEN = "oauthasTokenEndpoint";
private static final String DESTINATION_AUTHZ_MGMT = "authzMgmtService";
private static final String PROPERTY_CLIENTID = "User";
private static final String PROPERTY_SECRET = "Password";
private static final String ON_PREMISE_PROXY = "OnPremise";
// used for user to role assignments
private static class Role {
public String applicationName;
public String name;
public String providerAccount;
}
private static class Roles {
public Roles() {
this.role = new ArrayList();
}
@JsonProperty("roles")
public List role;
}
public static DataSource getDataSource() {
Object dataSource;
try {
InitialContext ctx = new InitialContext();
dataSource = ctx.lookup(JNDI_KEY_DATA_SOURCE);
} catch (NamingException e) {
throw new IllegalStateException("JNDI lookup failure", e);
}
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
throw new IllegalStateException(
"No data source available in JNDI context");
}
public static String getAccountName() {
TenantContext context = null;
try {
context = (TenantContext) (new InitialContext()).lookup("java:comp/env/tenantContext");
}
catch (NamingException ne) {
LOGGER.error("Failed to get tenant context", ne);
}
return context.getTenant().getAccount().getId();
}
public static Proxy getProxy(String proxyType) {
String proxyHost = null;
int proxyPort;
if (ON_PREMISE_PROXY.equals(proxyType)) {
// Get proxy for on-premise destinations
proxyHost = System.getenv("HC_OP_HTTP_PROXY_HOST");
proxyPort = Integer.parseInt(System.getenv("HC_OP_HTTP_PROXY_PORT"));
} else {
// Get proxy for internet destinations
proxyHost = System.getProperty("http.proxyHost");
proxyPort = Integer.parseInt(System.getProperty("http.proxyPort"));
}
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
}
public static int assignUserToRole(String userid, String rolename, String accessToken) {
// https://api.hana.ondemand.com/authorization/v1/documentation#accounts__accountName__users_roles_put
Role newRole = new Role();
newRole.applicationName = "xproject";
newRole.name = rolename;
newRole.providerAccount = getAccountName();
Roles roles = new Roles();
roles.role.add(newRole);
int responseCode = 0;
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(roles);
// look up the connectivity configuration API "connectivityConfiguration"
Context ctx = new InitialContext();
ConnectivityConfiguration configuration = (ConnectivityConfiguration) ctx.lookup(JNDI_KEY_CONNECTIVITY_CONFIG);
// get destination configuration for "authzapi"
DestinationConfiguration destConfiguration = configuration.getConfiguration(DESTINATION_AUTHZ_MGMT);
// get destination URL
String url = destConfiguration.getProperty("URL").replace("accountName", Util.getAccountName()).
concat("?userId=" + URLEncoder.encode(userid, StandardCharsets.UTF_8.name()));
LOGGER.debug("Usign URL {}", url);
// create HTTP Client and PUT request from Apache libs
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPut httpPut = new HttpPut(url);
// add API request body
StringEntity requestEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPut.setEntity(requestEntity);
LOGGER.debug("Sending API request body: {}", requestEntity.toString());
// add the OAuth Access Token to the header
httpPut.addHeader("Authorization", "Bearer " + accessToken);
try {
// call API
LOGGER.debug("Executing request {}", httpPut.getRequestLine());
CloseableHttpResponse response = httpClient.execute(httpPut);
responseCode = response.getStatusLine().getStatusCode();
HttpEntity responseBody = response.getEntity();
try {
LOGGER.debug("Response code: {}", responseCode);
if (responseCode == HttpURLConnection.HTTP_CREATED) {
// process response from api
String apiResponse = EntityUtils.toString(responseBody);
LOGGER.debug("Response is: {}", apiResponse);
}
} finally {
// complete response processing
EntityUtils.consume(responseBody);
response.close();
}
} catch (ClientProtocolException cpe) {
LOGGER.error("ClientProtocol Exception: ", cpe);
}
finally {
httpClient.close();
}
} catch (JsonProcessingException jpe) {
LOGGER.error("Error assigning the role: ", jpe);
} catch (IOException ioe) {
LOGGER.error("IO Exception: ", ioe);
} catch (NamingException ne) {
LOGGER.error("JNDI lookup failure", ne);
}
return responseCode;
}
public static int unassignUserFromRole(String userid, String rolename, String accessToken) {
// https://api.hana.ondemand.com/authorization/v1/documentation#accounts__accountName__users_roles_delete
int responseCode = 0;
try {
// look up the connectivity configuration API "connectivityConfiguration"
Context ctx = new InitialContext();
ConnectivityConfiguration configuration = (ConnectivityConfiguration) ctx.lookup(JNDI_KEY_CONNECTIVITY_CONFIG);
// get destination configuration for "authzapi"
DestinationConfiguration destConfiguration = configuration.getConfiguration(DESTINATION_AUTHZ_MGMT);
// get destination URL
String url = destConfiguration.getProperty("URL").replace("accountName", Util.getAccountName()).
concat("?userId=" + URLEncoder.encode(userid, StandardCharsets.UTF_8.name())).
concat("&roles=" + URLEncoder.encode(rolename + "@" + Util.getAccountName() + ":" + "xproject",StandardCharsets.UTF_8.name()));
LOGGER.debug("Usign URL {}", url);
// create HTTP Client and DELETE request from Apache libs
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpDelete httpDelete = new HttpDelete(url);
// add the OAuth Access Token to the header
httpDelete.addHeader("Authorization", "Bearer " + accessToken);
try {
// call API
LOGGER.debug("Executing request {}", httpDelete.getRequestLine());
CloseableHttpResponse response = httpClient.execute(httpDelete);
responseCode = response.getStatusLine().getStatusCode();
try {
HttpEntity responseBody = response.getEntity();
LOGGER.debug("Response code: {}", responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// process response from api
String apiResponse = EntityUtils.toString(responseBody);
LOGGER.debug("Response is: {}", apiResponse);
}
// complete response processing
EntityUtils.consume(responseBody);
} finally {
response.close();
}
} catch (ClientProtocolException cpe) {
LOGGER.error("ClientProtocol Exception: ", cpe);
}
finally {
httpClient.close();
}
} catch (JsonProcessingException jpe) {
LOGGER.error("Error assigning the role: ", jpe);
} catch (IOException ioe) {
LOGGER.error("IO Exception: ", ioe);
} catch (NamingException ne) {
LOGGER.error("JNDI lookup failure", ne);
}
return responseCode;
}
public static String requestNewAccessTokenForPlatformAPI() {
String accessToken = null;
try {
// look up the connectivity configuration API "connectivityConfiguration"
Context ctx = new InitialContext();
ConnectivityConfiguration configuration = (ConnectivityConfiguration) ctx.lookup(JNDI_KEY_CONNECTIVITY_CONFIG);
// get destination configuration for "oauthasTokenEndpoint"
DestinationConfiguration destConfiguration = configuration.getConfiguration(DESTINATION_OAUTHAS_TOKEN);
// get all destination properties
Map allDestinationPropeties = destConfiguration.getAllProperties();
// get clientid and secret from destination user and password properties
String clientid = allDestinationPropeties.get(Util.PROPERTY_CLIENTID);
String secret = allDestinationPropeties.get(Util.PROPERTY_SECRET);
LOGGER.debug("Usign client id {} and secret {}", clientid ,secret);
// get destination URL
URL url = new URL(allDestinationPropeties.get("URL"));
LOGGER.debug("Usign URL {}", url.toString());
// create HTTP Client and POST request from Apache libs
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url.toString());
// add OAuth access token request parameters as per https://tools.ietf.org/html/rfc6749#section-4.4.1
List nvps = new ArrayList();
nvps.add(new BasicNameValuePair("grant_type", "client_credentials"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
try {
// create Basic Authn header
UsernamePasswordCredentials creds =
new UsernamePasswordCredentials(clientid, secret);
httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null));
// send access token request to SAP CP Neo OAuth 2.0 Authorization Server
LOGGER.debug("Executing request {}", httpPost.getRequestLine());
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
HttpEntity responseBody = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode();
LOGGER.debug("Response code: {}", responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// process response from api token endpoint
String apiTokenResponse = EntityUtils.toString(responseBody);
LOGGER.debug("Response is: {}", apiTokenResponse);
// read api token response
ObjectMapper mapper = new ObjectMapper();
JsonNode apiToken = mapper.readTree(apiTokenResponse);
// get access token from response
accessToken = apiToken.findValue("access_token").textValue();
LOGGER.debug("Access token: {}", accessToken);
// store access token in SAP CP password storage using the account- and API name as alias
setAccessToken(getAccountName(), accessToken.toCharArray());
}
// complete response processing
EntityUtils.consume(responseBody);
} finally {
response.close();
}
} catch (ClientProtocolException cpe) {
LOGGER.error("ClientProtocol Exception: ", cpe);
} catch (AuthenticationException ae) {
LOGGER.error("Authentication Exception: ", ae);
}
finally {
httpClient.close();
}
} catch (Exception e) {
LOGGER.error("Exception: ", e);
}
return accessToken;
}
public static String getAccessTokenForPlatformAPI() {
String accessToken = null;
try {
char[] accessTokenChar = getAccessToken(getAccountName());
if (accessTokenChar != null) {
accessToken = String.valueOf(accessTokenChar);
} else {
// request a new access token from SAP CP Neo OAuth 2.0 Authorization Server
accessToken = requestNewAccessTokenForPlatformAPI();
}
} catch (PasswordStorageException pse) {
LOGGER.error("Error retrieving access token from password storage: ", pse);
} catch (NamingException ne) {
LOGGER.error("JNDI lookup failure", ne);
}
return accessToken;
}
private static PasswordStorage getPasswordStorage() throws NamingException {
InitialContext ctx = new InitialContext();
PasswordStorage passwordStorage = (PasswordStorage) ctx.lookup(JNDI_KEY_PASSWORD_STORAGE);
return passwordStorage;
}
private static void setAccessToken(String alias, char[] accessToken) throws PasswordStorageException, NamingException {
// TODO: Place cursor below this line and insert Snippet 1
PasswordStorage passwordStorage = getPasswordStorage();
passwordStorage.setPassword(alias, accessToken);
LOGGER.debug("Successfully stored access token with account id {} as alias", alias);
}
private static char[] getAccessToken(String alias) throws PasswordStorageException, NamingException {
char[] accessToken = null;
// TODO: Place cursor below this line and insert Snippet 2
PasswordStorage passwordStorage = getPasswordStorage();
accessToken = passwordStorage.getPassword(alias);
if (accessToken != null) {
LOGGER.debug("Retrieved access token {} for account id {} as alias", String.valueOf(accessToken), alias);
}
return accessToken;
}
}
}}}
== DX360 ==
* Sorter does not use the function
* use expander parameters after binding (bind)