-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge-request: EDU-MR-3368 Merged-by: Sviatoslav Naiden <[email protected]>
- Loading branch information
1 parent
3d21db4
commit 94f61c8
Showing
31 changed files
with
584 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
...ij-plugin/educational-core/resources/fileTemplates/internal/linkedin.redirectPage.html.ft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<html> | ||
<head> | ||
<title>Redirecting to LinkedIn</title> | ||
<meta http-equiv="refresh" content="0.1; URL=https://www.linkedin.com/feed/"> | ||
<meta name="keywords" content="automatic redirection"> | ||
</head> | ||
|
||
<body> | ||
If your browser doesn't automatically redirect within a few seconds, | ||
you may want to go to | ||
<a href="https://www.linkedin.com/feed/">the destination</a> manually. | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
...plugin/educational-core/src/com/jetbrains/edu/learning/socialmedia/SocialMediaSettings.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.jetbrains.edu.learning.socialmedia | ||
|
||
import com.intellij.openapi.components.BaseState | ||
import com.intellij.openapi.components.SimplePersistentStateComponent | ||
import com.jetbrains.edu.learning.isUnitTestMode | ||
|
||
abstract class SocialMediaSettings<out T : SocialMediaSettings.SocialMediaSettingsState>(state: T) : | ||
SimplePersistentStateComponent<@UnsafeVariance T>(state) { | ||
|
||
// Don't use property delegation like `var askToTweet by state::askToPost`. | ||
// It doesn't work because `state` may change but delegation keeps the initial state object | ||
var askToPost: Boolean | ||
get() = state.askToPost | ||
set(value) { | ||
state.askToPost = value | ||
} | ||
|
||
var userId: String | ||
get() { | ||
return state.userId ?: getDefaultUserId() | ||
} | ||
set(userId) { | ||
state.userId = userId | ||
} | ||
|
||
abstract fun getDefaultUserId(): String | ||
|
||
open class SocialMediaSettingsState : BaseState() { | ||
var userId by string() | ||
var askToPost by property(!isUnitTestMode) | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
...educational-core/src/com/jetbrains/edu/learning/socialmedia/linkedIn/LinkedInConnector.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.jetbrains.edu.learning.socialmedia.linkedIn | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.databind.module.SimpleModule | ||
import com.intellij.openapi.components.Service | ||
import com.intellij.openapi.components.service | ||
import com.jetbrains.edu.learning.api.EduOAuthCodeFlowConnector | ||
import com.jetbrains.edu.learning.authUtils.ConnectorUtils | ||
import com.jetbrains.edu.learning.network.executeHandlingExceptions | ||
import org.apache.http.client.utils.URIBuilder | ||
|
||
@Service | ||
class LinkedInConnector : EduOAuthCodeFlowConnector<LinkedInAccount, LinkedInUserInfo>() { | ||
override val authorizationUrlBuilder: URIBuilder | ||
get() = URIBuilder(LINKEDIN_BASE_WWW_URL) | ||
.setPath("/oauth/v2/authorization") | ||
.addParameter(CLIENT_ID_PARAM_NAME, CLIENT_ID) | ||
.addParameter(GRANT_TYPE, "code") | ||
.addParameter(REDIRECT_URL, getRedirectUri()) | ||
.addParameter(RESPONSE_TYPE, "code") | ||
.addParameter(SCOPE, "w_member_social openid profile") | ||
|
||
override val baseOAuthTokenUrl: String | ||
get() = "/oauth/v2/accessToken" | ||
|
||
override val baseUrl: String = LINKEDIN_BASE_WWW_URL | ||
override val clientId: String = CLIENT_ID | ||
override val clientSecret: String = CLIENT_SECRET | ||
override val objectMapper: ObjectMapper by lazy { | ||
ConnectorUtils.createRegisteredMapper(SimpleModule()) | ||
} | ||
override val platformName: String = LINKEDIN | ||
|
||
override var account: LinkedInAccount? | ||
get() { | ||
return LinkedInSettings.getInstance().account | ||
} | ||
set(account) { | ||
LinkedInSettings.getInstance().account = account | ||
} | ||
|
||
override fun getUserInfo(account: LinkedInAccount, accessToken: String?): LinkedInUserInfo? { | ||
val response = | ||
getEndpoints<LinkedInEndpoints>(account, accessToken, baseUrl = LINKEDIN_API_URL).getCurrentUserInfo().executeHandlingExceptions() | ||
return response?.body() | ||
} | ||
|
||
@Synchronized | ||
override fun login(code: String): Boolean { | ||
val tokenInfo = retrieveLoginToken(code, getRedirectUri(), codeVerifierFieldName = CODE_VERIFIER_PARAM_NAME) ?: return false | ||
val account = LinkedInAccount(tokenInfo.expiresIn) | ||
val currentUser = getUserInfo(account, tokenInfo.accessToken) ?: return false | ||
account.userInfo = currentUser | ||
account.saveTokens(tokenInfo) | ||
this.account = account | ||
notifyUserLoggedIn() | ||
return true | ||
} | ||
|
||
companion object { | ||
private val CLIENT_ID: String = LinkedInOAuthBundle.value("linkedInClientId") | ||
private val CLIENT_SECRET: String = LinkedInOAuthBundle.value("linkedInClientSecret") | ||
private const val LINKEDIN_API_URL = "https://api.linkedin.com" | ||
const val LINKEDIN_BASE_WWW_URL = "https://www.linkedin.com" | ||
const val LINKEDIN = "LinkedIn" | ||
|
||
private const val CLIENT_ID_PARAM_NAME = "client_id" | ||
private const val CODE_VERIFIER_PARAM_NAME = "state" | ||
private const val GRANT_TYPE = "grant_type" | ||
private const val REDIRECT_URL = "redirect_uri" | ||
private const val RESPONSE_TYPE = "response_type" | ||
private const val SCOPE = "scope" | ||
|
||
|
||
fun getInstance(): LinkedInConnector = service() | ||
} | ||
|
||
} |
9 changes: 9 additions & 0 deletions
9
...educational-core/src/com/jetbrains/edu/learning/socialmedia/linkedIn/LinkedInEndpoints.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.jetbrains.edu.learning.socialmedia.linkedIn | ||
|
||
import retrofit2.Call | ||
import retrofit2.http.GET | ||
|
||
interface LinkedInEndpoints { | ||
@GET("/v2/userinfo") | ||
fun getCurrentUserInfo(): Call<LinkedInUserInfo> | ||
} |
14 changes: 14 additions & 0 deletions
14
...ucational-core/src/com/jetbrains/edu/learning/socialmedia/linkedIn/LinkedInOauthBundle.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.jetbrains.edu.learning.socialmedia.linkedIn | ||
|
||
import com.jetbrains.edu.learning.messages.EduPropertiesBundle | ||
import org.jetbrains.annotations.NonNls | ||
import org.jetbrains.annotations.PropertyKey | ||
|
||
@NonNls | ||
private const val BUNDLE_NAME = "linkedin.linkedin-oauth" | ||
|
||
object LinkedInOAuthBundle : EduPropertiesBundle(BUNDLE_NAME) { | ||
fun value(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String): String { | ||
return valueOrEmpty(key) | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...ional-core/src/com/jetbrains/edu/learning/socialmedia/linkedIn/LinkedInOptionsProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.jetbrains.edu.learning.socialmedia.linkedIn | ||
|
||
import com.intellij.openapi.options.BoundConfigurable | ||
import com.intellij.openapi.ui.DialogPanel | ||
import com.intellij.ui.dsl.builder.bindSelected | ||
import com.intellij.ui.dsl.builder.panel | ||
import com.jetbrains.edu.learning.messages.EduCoreBundle | ||
import com.jetbrains.edu.learning.settings.OptionsProvider | ||
|
||
class LinkedInOptionsProvider : BoundConfigurable(EduCoreBundle.message("linkedin.configurable.name")), OptionsProvider { | ||
|
||
override fun createPanel(): DialogPanel = panel { | ||
group(displayName) { | ||
row { | ||
checkBox(EduCoreBundle.message("linkedin.ask.to.post")) | ||
.bindSelected(LinkedInSettings.getInstance()::askToPost) | ||
} | ||
} | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
...ucational-core/src/com/jetbrains/edu/learning/socialmedia/linkedIn/LinkedInRestService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.jetbrains.edu.learning.socialmedia.linkedIn | ||
|
||
import com.intellij.util.io.origin | ||
import com.jetbrains.edu.learning.* | ||
import com.jetbrains.edu.learning.authUtils.* | ||
import com.jetbrains.edu.learning.courseFormat.EduFormatNames.CODE_ARGUMENT | ||
import com.jetbrains.edu.learning.courseGeneration.GeneratorUtils.getInternalTemplateText | ||
import io.netty.channel.ChannelHandlerContext | ||
import io.netty.handler.codec.http.* | ||
import org.jetbrains.io.send | ||
import java.io.IOException | ||
import java.lang.reflect.InvocationTargetException | ||
|
||
class LinkedInRestService : OAuthRestService("LinkedIn") { | ||
|
||
@Throws(InterruptedException::class, InvocationTargetException::class) | ||
override fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean { | ||
return if (request.method() === HttpMethod.GET | ||
// If isOriginAllowed is `false` check if it is a valid oAuth request with empty origin | ||
&& ((isOriginAllowed(request) === OriginCheckResult.ALLOW || LinkedInConnector.getInstance() | ||
.isValidOAuthRequest(request, urlDecoder))) | ||
) { | ||
true | ||
} | ||
else { | ||
super.isHostTrusted(request, urlDecoder) | ||
} | ||
} | ||
|
||
@Throws(IOException::class) | ||
override fun execute( | ||
urlDecoder: QueryStringDecoder, | ||
request: FullHttpRequest, | ||
context: ChannelHandlerContext | ||
): String? { | ||
val uri = urlDecoder.uri() | ||
|
||
if (LinkedInConnector.getInstance().getOAuthPattern("\\?error=(\\w+)").matcher(uri).matches()) { | ||
return sendErrorResponse(request, context, "Failed to log in") | ||
} | ||
|
||
if (LinkedInConnector.getInstance().getOAuthPattern().matcher(uri).matches()) { | ||
val code = getStringParameter(CODE_ARGUMENT, urlDecoder)!! // cannot be null because of pattern | ||
val success = LinkedInConnector.getInstance().login(code) | ||
if (success) { | ||
LOG.info("$platformName: OAuth code is handled") | ||
val pageContent = getInternalTemplateText("linkedin.redirectPage.html") | ||
createResponse(pageContent).send(context.channel(), request) | ||
return null | ||
} | ||
return sendErrorResponse(request, context, "Failed to log in using provided code") | ||
} | ||
|
||
sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel()) | ||
return "Unknown command: $uri" | ||
} | ||
|
||
override fun getServiceName(): String = LinkedInConnector.getInstance().serviceName | ||
|
||
override fun isOriginAllowed(request: HttpRequest): OriginCheckResult { | ||
val originAllowed = super.isOriginAllowed(request) | ||
if (originAllowed == OriginCheckResult.FORBID) { | ||
val origin = request.origin ?: return OriginCheckResult.FORBID | ||
if (origin == LinkedInConnector.LINKEDIN_BASE_WWW_URL) { | ||
return OriginCheckResult.ALLOW | ||
} | ||
} | ||
return originAllowed | ||
} | ||
} |
Oops, something went wrong.