Skip to content

Commit

Permalink
Merge pull request #27 from gofynd/add/admin-routes-support
Browse files Browse the repository at this point in the history
ID: FPCO-25842; Add admin routes support
  • Loading branch information
vivek-gofynd committed Feb 7, 2024
2 parents 5e51e05 + 7ab239e commit 13ff843
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 6 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,35 @@ public class ExampleOfflineAccessMode {
}
```

#### How to call partner apis?

To call partner api you need to have instance of `PartnerClient`. Instance holds methods for SDK classes.

extend `BasePartnerController` class to create controller which will add `PartnerClient` in request.

```java
@RestController
@RequestMapping("/api/v1")
@Slf4j
public class PartnerController extends BasePartnerController {

@GetMapping(value = "/orgThemes", produces = "application/json")
public ThemePartnerModels.MarketplaceThemeSchema getOrgThemes(HttpServletRequest request) {
try {
PartnerClient partnerClient = (PartnerClient) request.getAttribute("partnerClient");
ThemePartnerModels.MarketplaceThemeSchema orgThemes = partnerClient.theme.getOrganizationThemes("published", null, null);

return orgThemes;

} catch (Exception e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}

}
}
```

#### How to register for Webhook Events?

Webhook events can be helpful to handle tasks when certain events occur on platform. You can subscribe to such events by passing **webhook** in Extension Configuration Property
Expand Down
Empty file modified mvnw
100644 → 100755
Empty file.
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
</parent>
<groupId>com.fynd</groupId>
<artifactId>fynd-extension-java</artifactId>
<version>0.5.0-beta.1</version>
<version>0.6.0</version>
<name>fynd-extension-java</name>
<description>Java Fynd Extension Library</description>
<properties>
<java.version>17</java.version>
<json.version>20211205</json.version>
<jedis.version>4.3.1</jedis.version>
<commons.version>1.7</commons.version>
<fdk-client.version>v1.0.0</fdk-client.version>
<fdk-client.version>1.3.11-beta.4</fdk-client.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -33,6 +33,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.fynd.extension.controllers;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class BasePartnerController {


// will be used by the extension developer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package com.fynd.extension.controllers;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.DefaultUriBuilderFactory;

import com.fynd.extension.error.FdkInvalidOAuth;
import com.fynd.extension.error.FdkSessionNotFound;
import com.fynd.extension.middleware.AccessMode;
import com.fynd.extension.middleware.FdkConstants;
import com.fynd.extension.model.Extension;
import com.fynd.extension.model.Option;
import com.fynd.extension.model.Response;
import com.fynd.extension.session.Session;
import com.fynd.extension.session.SessionStorage;
import com.sdk.common.model.AccessTokenDto;
import com.sdk.partner.PartnerConfig;
import lombok.extern.slf4j.Slf4j;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/adm")
@Slf4j
public class ExtensionADMController {
@Autowired
Extension ext;

@Autowired
SessionStorage sessionStorage;

@GetMapping(path = "/install")
public ResponseEntity<?> install(@RequestParam(value = "organization_id") String organizationId,
HttpServletResponse response, HttpServletRequest request) {

try {
log.info("/adm/install invoked");
if (StringUtils.isEmpty(organizationId)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Invalid organization id");
}

PartnerConfig partnerConfig = ext.getPartnerConfig(organizationId);
Session session = new Session(Session.generateSessionId(true, null), true);
Date sessionExpires = Date.from(Instant.now()
.plusMillis(Fields.MINUTES_LIMIT));
if (session.isNew()) {
session.setOrganizationId(organizationId);
session.setScope(ext.getExtensionProperties().getScopes());
session.setExpires(FdkConstants.DATE_FORMAT.get()
.format(sessionExpires));
session.setExpiresIn(sessionExpires.getTime());
session.setAccessMode(
AccessMode.ONLINE.getName()); // Always generate online mode token for extension launch
session.setExtensionId(ext.getExtensionProperties()
.getApiKey());
} else {
if (!StringUtils.isEmpty(session.getExpires())) {
session.setExpires(FdkConstants.DATE_FORMAT.get()
.format(session.getExpires()));
session.setExpiresIn(sessionExpires.getTime());
}
}
ResponseCookie resCookie = ResponseCookie.from(FdkConstants.ADMIN_SESSION_COOKIE_NAME, session.getId())
.httpOnly(true)
.sameSite("None")
.secure(true)
.path("/")
.maxAge(Duration.between(Instant.now(), Instant.ofEpochMilli(
session.getExpiresIn())))
.build();

session.setState(UUID.randomUUID()
.toString());
String baseUrl = ext.getExtensionProperties().getBaseUrl();
var uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
String authCallback = uriBuilderFactory.builder().pathSegment("adm/auth").build().toString();
System.out.println("authCallback " + authCallback);
String redirectUrl = partnerConfig.getPartnerOauthClient()
.getAuthorizationURL(session.getScope(), authCallback,
session.getState(),
true); // Always generate online mode token for extension launch
sessionStorage.saveSession(session);
request.setAttribute("session", session);
return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT)
.header(HttpHeaders.LOCATION, redirectUrl)
.header(HttpHeaders.SET_COOKIE, resCookie.toString())
.build();
} catch (Exception error) {
log.error("Exception in install call ", error);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new Response(false, error.getMessage()));
}

}

@GetMapping(path = "/auth")
public ResponseEntity<?> authorize(@RequestParam(value = "organization_id") String organizationId,
@RequestParam(value = "code", required = false) String code,
@RequestParam(value = "state") String state,
HttpServletRequest request, HttpServletResponse response) {

try {
String sessionIdForOrganization = ext.getCookieValue(request.getCookies());
if (StringUtils.isNotEmpty(sessionIdForOrganization)) {
Session fdkSession = sessionStorage.getSession(sessionIdForOrganization);
if (Objects.isNull(fdkSession)) {
throw new FdkSessionNotFound("Can not complete oauth process as session not found");
}
if (!fdkSession.getState()
.equalsIgnoreCase(state)) {
throw new FdkInvalidOAuth("Invalid oauth call");
}
PartnerConfig partnerConfig = ext.getPartnerConfig(fdkSession.getOrganizationId());
partnerConfig.getPartnerOauthClient()
.verifyCallback(code);

AccessTokenDto token = partnerConfig.getPartnerOauthClient()
.getRawToken();
Date sessionExpires = Date.from(Instant.now()
.plusMillis(token.getExpiresIn() * 1000));
fdkSession.setExpires(FdkConstants.DATE_FORMAT.get()
.format(sessionExpires));
token.setAccessTokenValidity(sessionExpires.getTime());
Session.updateToken(token, fdkSession);
sessionStorage.saveSession(fdkSession);
request.setAttribute("session", fdkSession);
// Generate separate access token for offline mode
if (!ext.isOnlineAccessMode()) {
String sid = Session.generateSessionId(false, new Option(organizationId, ext.getExtensionProperties()
.getCluster()));
Session session = sessionStorage.getSession(sid);
log.debug("Retrieving session in ExtensionController.authorize() : {}", session);
if (ObjectUtils.isEmpty(session) || (!Objects.equals(session.getExtensionId(),
ext.getExtensionProperties()
.getApiKey()))) {
session = new Session(sid, true);
}
AccessTokenDto offlineTokenRes = partnerConfig.getPartnerOauthClient()
.getOfflineAccessToken(String.join(",", ext.getExtensionProperties().getScopes()), code);
session.setOrganizationId(organizationId);
session.setScope(ext.getExtensionProperties().getScopes());
session.setState(fdkSession.getState());
session.setExtensionId(ext.getExtensionProperties()
.getApiKey());
offlineTokenRes.setAccessTokenValidity(partnerConfig.getPartnerOauthClient()
.getTokenExpiresAt());
offlineTokenRes.setAccessMode(AccessMode.OFFLINE.getName());
Session.updateToken(offlineTokenRes, session);
log.debug("Saving session from ExtensionController.authorize() : {}", session);
sessionStorage.saveSession(session);
} else {
fdkSession.setExpires(null);
}
ResponseCookie resCookie = ResponseCookie.from(FdkConstants.ADMIN_SESSION_COOKIE_NAME, fdkSession.getId())
.httpOnly(true)
.sameSite("None")
.secure(true)
.path("/")
.maxAge(Duration.between(Instant.now(), Instant.ofEpochMilli(
fdkSession.getExpiresIn())))
.build();
String baseUrl = ext.getExtensionProperties().getBaseUrl();
var uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
String admLaunchCallback = uriBuilderFactory.builder().pathSegment("admin").build().toString();
return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT)
.header(HttpHeaders.LOCATION, admLaunchCallback)
.header(HttpHeaders.SET_COOKIE, resCookie.toString())
.build();
}
} catch (Exception error) {
log.error("Exception in auth call ", error);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new Response(false, error.getMessage()));
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new Response(false, "Failed due to empty Session ID"));
}

public interface Fields {
int MINUTES_LIMIT = 900000;
String DELIMITER = "_";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fynd.extension.controllers.BaseApplicationController;
import com.fynd.extension.controllers.BasePartnerController;
import com.fynd.extension.controllers.BasePlatformController;
import com.fynd.extension.model.*;
import com.fynd.extension.session.Session;
import com.sdk.application.ApplicationClient;
import com.sdk.application.ApplicationConfig;
import com.sdk.partner.PartnerClient;
import com.sdk.platform.PlatformClient;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -33,6 +35,9 @@ public class ControllerInterceptor implements HandlerInterceptor {
@Autowired
SessionInterceptor sessionInterceptor;

@Autowired
PartnerSessionInterceptor partnerSessionInterceptor;

@Override
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
try {
Expand Down Expand Up @@ -78,6 +83,19 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl
}
return true;
}
else if(controller instanceof BasePartnerController){

boolean isSessionInterceptorPassed = partnerSessionInterceptor.preHandle(request, response, handler);

log.info("[PARTNER INTERCEPTOR]");
Session fdkSession = (Session) request.getAttribute("fdkSession");
PartnerClient partnerClient = extension.getPartnerClient(fdkSession.getOrganizationId(), fdkSession);

request.setAttribute("partnerClient", partnerClient);
request.setAttribute("extension", extension);

return isSessionInterceptorPassed;
}
}


Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/fynd/extension/middleware/FdkConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
public class FdkConstants {

public static final String SESSION_COOKIE_NAME = "ext_session";
public static final String ADMIN_SESSION_COOKIE_NAME = "ext_adm_session";


public static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.fynd.extension.middleware;

import com.fynd.extension.session.Session;
import com.fynd.extension.session.SessionStorage;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Arrays;
import java.util.Optional;

import static com.fynd.extension.middleware.FdkConstants.ADMIN_SESSION_COOKIE_NAME;

@Slf4j
@Component
public class PartnerSessionInterceptor implements HandlerInterceptor{

@Autowired
SessionStorage sessionStorage;


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("[PARTNER SESSION INTERCEPTOR]");
Session fdkSession = null;

Optional<Cookie> sessionCookie = Arrays.stream(request.getCookies())
.filter(c -> c.getName().equals(ADMIN_SESSION_COOKIE_NAME))
.findFirst();

if(sessionCookie.isPresent()){
String sessionId = sessionCookie.map(Cookie::getValue).orElse(null);
fdkSession = sessionStorage.getSession(sessionId);
}

if (ObjectUtils.isNotEmpty(fdkSession)) {
request.setAttribute("fdkSession", fdkSession);
return true;
} else {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized");
}
}
}
Loading

0 comments on commit 13ff843

Please sign in to comment.