Skip to content

Commit

Permalink
feat: Linkedin Connector - Meeds-io/MIPs#141
Browse files Browse the repository at this point in the history
Work in Progress
  • Loading branch information
sergeByishimo committed Sep 17, 2024
1 parent 03e1b41 commit 211603a
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This file is part of the Meeds project (https://meeds.io/).
*
* Copyright (C) 2020 - 2024 Meeds Association [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package io.meeds.linkedin.gamification.exception;

public class LinkedinConnectionException extends Exception {

private static final long serialVersionUID = -2437500058122638710L;

public LinkedinConnectionException(String e) {
super(e);
}

public LinkedinConnectionException(String message, Exception e) {
super(message, e);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.meeds.linkedin.gamification.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RemoteOrganization implements Cloneable {

private long id;
private String vanityName;
private String localizedName;

@Override
public RemoteOrganization clone() {
return new RemoteOrganization(id, vanityName, localizedName);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.meeds.linkedin.gamification.rest;

import io.meeds.gamification.model.RemoteConnectorSettings;
import io.meeds.linkedin.gamification.model.RemoteOrganization;
import io.meeds.linkedin.gamification.services.LinkedinService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -11,15 +12,18 @@
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.annotation.Secured;
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.server.ResponseStatusException;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;

@RestController
@RequestMapping("settings")
Expand All @@ -32,50 +36,77 @@ public class LinkedinSettingsRest {
private LinkedinService linkedinService;


@GetMapping("check-secret")
// @GetMapping("check-secret")
// @Secured("rewarding")
// @Produces(MediaType.APPLICATION_JSON)
// @Operation(summary = "Get LinkedIn connector settings", method = "GET")
// @ApiResponses(value = {
// @ApiResponse(responseCode = "200", description = "Request fulfilled"),
// @ApiResponse(responseCode = "400", description = "Invalid query input"),
// @ApiResponse(responseCode = "401", description = "Unauthorized operation"),
// @ApiResponse(responseCode = "500", description = "Internal server error")
// })
// public Response getSettings() throws IllegalAccessException {
//
// LOG.debug("LinkedinSettingsRest.getSettings: started");
//
// org.exoplatform.services.security.Identity identity = ConversationState.getCurrent().getIdentity();
// RemoteConnectorSettings remoteConnectorSettings = linkedinService.getConnectorSettings(identity);
//
// boolean hasSecret = remoteConnectorSettings.getSecretKey() != null;
//
// return Response.ok().entity("{\"hasSecret\": " + hasSecret + "}").build();
// }

@GetMapping("oauthCallback")
@Secured("rewarding")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Get LinkedIn connector settings", method = "GET")
@Produces(MediaType.TEXT_HTML)
@Operation(summary = "Validate OAuth code returned from linkedin.", method = "GET")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Request fulfilled"),
@ApiResponse(responseCode = "400", description = "Invalid query input"),
@ApiResponse(responseCode = "401", description = "Unauthorized operation"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public Response getSettings() throws IllegalAccessException {
public String validateOathCode(
@Parameter(description = "Access Token", required = true)
@RequestParam(value = "code", required = true)
String code) throws IllegalAccessException {

LOG.debug("LinkedinSettingsRest.getSettings: started");
LOG.debug("LinkedinSettingsRest.connect: started");
LOG.debug("LinkedinSettingsRest.connect: code : " + code);

org.exoplatform.services.security.Identity identity = ConversationState.getCurrent().getIdentity();
RemoteConnectorSettings remoteConnectorSettings = linkedinService.getConnectorSettings(identity);

boolean hasSecret = remoteConnectorSettings.getSecretKey() != null;
linkedinService.validateOathCode(identity, code);

return Response.ok().entity("{\"hasSecret\": " + hasSecret + "}").build();
return "<html><body><script type=text/javascript>window.close();</script></body></html>";
}

@GetMapping("oauthCallback")

@GetMapping("remote/organization/lookup")
@Secured("rewarding")
@Produces(MediaType.TEXT_HTML)
@Operation(summary = "Validate Remote user identifier on a selected connector and associate it in his current profile.", description = "Validate Remote user identifier on a selected connector and associate it in his current profile.", method = "POST")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Find Organization by Vanity Name", method = "GET")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Request fulfilled"),
@ApiResponse(responseCode = "400", description = "Invalid query input"),
@ApiResponse(responseCode = "401", description = "Unauthorized operation"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
public String validateOathCode(
@Parameter(description = "Access Token", required = true)
@RequestParam(value = "code", required = true)
String code) throws IllegalAccessException {
public List<RemoteOrganization> searchOrganizationsByName(
@Parameter(description = "Search keyword", required = true)
@RequestParam(value = "keyword", required = true)
String keyword) {

LOG.debug("LinkedinSettingsRest.connect: started");
LOG.debug("LinkedinSettingsRest.connect: code : " + code);
LOG.debug("LinkedinSettingsRest.searchOrganizationsByVanityName: started");
LOG.debug("LinkedinSettingsRest.searchOrganizationsByVanityName: keyword : " + keyword);

org.exoplatform.services.security.Identity identity = ConversationState.getCurrent().getIdentity();

linkedinService.validateOathCode(identity, code);

return "<html><body><script type=text/javascript>window.close();</script></body></html>";
try {
return linkedinService.searchOrganizationsByName(keyword, identity);
} catch (IllegalAccessException e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
import io.meeds.gamification.model.RemoteConnectorSettings;
import io.meeds.gamification.service.ConnectorSettingService;
import io.meeds.gamification.utils.Utils;
import io.meeds.gamification.websocket.entity.ConnectorIdentifierModification;
import io.meeds.linkedin.gamification.model.RemoteOrganization;
import io.meeds.linkedin.gamification.plugin.LinkedInConnectorPlugin;
import io.meeds.linkedin.gamification.storage.LinkedinAccountStorage;
import io.meeds.linkedin.gamification.storage.LinkedinConsumerStorage;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.Identity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class LinkedinService {

Expand All @@ -31,21 +34,23 @@ public class LinkedinService {
private LinkedInConnectorPlugin linkedInConnectorPlugin;
@Autowired
private ListenerService listenerService;

public RemoteConnectorSettings getConnectorSettings(Identity identity) throws IllegalAccessException {
if (!Utils.isRewardingManager(identity.getUserId())) {
throw new IllegalAccessException("The user is not authorized to get LinkedIn connector settings");
}

LOG.debug("LinkedinService.getConnectorSettings: started");

return connectorSettingService.getConnectorSettings(
LinkedInConnectorPlugin.CONNECTOR_NAME);
}
@Autowired
private LinkedinConsumerStorage linkedinConsumerStorage;

// public RemoteConnectorSettings getConnectorSettings(Identity identity) throws IllegalAccessException {
// if (!Utils.isRewardingManager(identity.getUserId())) {
// throw new IllegalAccessException("The user is not authorized to get LinkedIn connector settings");
// }
//
// LOG.debug("LinkedinService.getConnectorSettings: started");
//
// return connectorSettingService.getConnectorSettings(
// LinkedInConnectorPlugin.CONNECTOR_NAME);
// }

public void validateOathCode(Identity identity, String code) throws IllegalAccessException {
if (!Utils.isRewardingManager(identity.getUserId())) {
throw new IllegalAccessException("The user is not authorized to save or update LinkedIn Bearer Token");
throw new IllegalAccessException("The user is not authorized to validate LinkedIn Bearer Token");
}

LOG.debug("SettingsService.validateOathCode: started");
Expand All @@ -62,20 +67,17 @@ public void validateOathCode(Identity identity, String code) throws IllegalAcces
LOG.debug("SettingsService.validateOathCode: getExpiresIn: " + oAuth2AccessToken.getExpiresIn());

linkedinAccountStorage.saveLinkedinBearerToken(oAuth2AccessToken.getAccessToken());

try {
listenerService.broadcast(TOKEN_CREATED_EVENT_NAME,
new ConnectorIdentifierModification("connectorIdentifierUpdated",
connectorName,
username,
connectorUserId),
username);
} catch (Exception e) {
LOG.warn("Error while broadcasting operation '{}' for connector {}", "connectorIdentifierUpdated", connectorName, e);
}
}

public String getLinkedinBearerToken() {
return linkedinAccountStorage.getLinkedinBearerToken();
}

public List<RemoteOrganization> searchOrganizationsByName(String keyword, Identity identity) throws IllegalAccessException {
if (!Utils.isRewardingManager(identity.getUserId())) {
throw new IllegalAccessException("The user is not authorized to search organizations by name");
}


return linkedinConsumerStorage.findOrganizationsByVanityName(this.getLinkedinBearerToken(), keyword);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.meeds.linkedin.gamification.storage;

import io.meeds.linkedin.gamification.services.LinkedinService;
import org.apache.commons.lang3.StringUtils;
import org.exoplatform.commons.api.settings.SettingService;
import org.exoplatform.commons.api.settings.SettingValue;
import org.exoplatform.commons.api.settings.data.Context;
Expand Down Expand Up @@ -31,11 +32,27 @@ public void saveLinkedinBearerToken(String bearerToken) {
LOG.debug("Finished saving LinkedIn Bearer Token");
}

public String getLinkedinBearerToken() {
SettingValue<?> settingValue = settingService.get(Context.GLOBAL, LINKEDIN_CONNECTOR_SCOPE, BEARER_TOKEN_KEY);
if (settingValue != null && settingValue.getValue() != null && StringUtils.isNotBlank(settingValue.getValue().toString())) {
return decode(settingValue.getValue().toString());
}
return null;
}

private String encode(String token) {
try {
return codecInitializer.getCodec().encode(token);
} catch (TokenServiceInitializationException e) {
throw new IllegalStateException("Error encrypting token", e);
}
}

private String decode(String encryptedToken) {
try {
return codecInitializer.getCodec().decode(encryptedToken);
} catch (TokenServiceInitializationException e) {
throw new IllegalStateException("Error decrypting token", e);
}
}
}
Loading

0 comments on commit 211603a

Please sign in to comment.