Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

Commit

Permalink
adding IAMIdentityCenter
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenton committed Oct 31, 2023
1 parent f38f908 commit 35237d7
Show file tree
Hide file tree
Showing 43 changed files with 835 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ changes with their rationale when appropriate. Given version `A.B.C.D`, breaking
### v5.2.6.0 (uncut)
- **http4k-connect-*** - Upgrade dependencies.
- **http4k-connect-*** - [Breaking - dev] http4k-connect is now built with Java 21.
- **http4k-connect-amazon-iamidentitycenter** - [New module] Adapter and fake implementation, plus interactive SSO login via a browser.

### v5.2.5.0
- **http4k-connect-*** - Upgrade dependencies.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ dependencies {
- [EventBridge](./amazon/eventbridge) -> `"org.http4k:http4k-connect-amazon-eventbridge"` / `"org.http4k:http4k-connect-amazon-eventbridge-fake"`
- [Evidently](./amazon/evidently) -> `"org.http4k:http4k-connect-amazon-evidently"` / `"org.http4k:http4k-connect-amazon-evidently-fake"`
- [Firehose](./amazon/firehose) -> `"org.http4k:http4k-connect-amazon-firehose"` / `"org.http4k:http4k-connect-amazon-firehose-fake"`
- [IAM Identity Center](./amazon/iamidentitycenter) -> `"org.http4k:http4k-connect-amazon-iamidentitycenter"` / `"org.http4k:http4k-connect-amazon-iamidentitycenter-fake"`
- [InstanceMetadataService](./amazon/instancemetadata) -> `"org.http4k:http4k-connect-amazon-instancemetadata"` / `"org.http4k:http4k-connect-amazon-instancemetadata-fake"`
- [KMS](./amazon/kms) -> `"org.http4k:http4k-connect-amazon-kms"` / `"org.http4k:http4k-connect-amazon-kms-fake"`
- [Lambda](./amazon/lambda) -> `"org.http4k:http4k-connect-amazon-lambda"` / `"org.http4k:http4k-connect-amazon-lambda-fake"`
Expand Down
46 changes: 46 additions & 0 deletions amazon/iamidentitycenter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# IAM Identity Center

The IAMIdentityCenter connector provides the following Fakes:

## OIDC

Actions:
* RegisterClient
* StartDeviceAuthentication
* CreateToken

### Default Fake port: 34160

To start:

```
FakeOIDC().start()
```

## SSO

Actions:
* SSO: GetFederatedCredentials

### Default Fake port: 25813

To start:

```
FakeSSO().start()
```

## Interactive CLI login

The module provides a CredentialsProvider to do interactive login to

```kotlin
val provider = CredentialsProvider.SSO(
SSOProfile(
AwsAccount.of("01234567890"),
RoleName.of("hello"),
Region.US_EAST_1,
Uri.of("http://foobar"),
)
)
```
12 changes: 12 additions & 0 deletions amazon/iamidentitycenter/client/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Libs.api

dependencies {
api(project(":http4k-connect-amazon-core"))
api(Libs.http4k_format_moshi) {
exclude("org.jetbrains.kotlin", "kotlin-reflect")
}
implementation(Libs.api)

testImplementation(project(path = ":http4k-connect-core", configuration = "testArtifacts"))
testImplementation(project(path = ":http4k-connect-amazon-core", configuration = "testArtifacts"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.http4k.connect.amazon.iamidentitycenter

import org.http4k.client.JavaHttpClient
import org.http4k.connect.amazon.core.model.Region
import org.http4k.core.HttpHandler
import org.http4k.core.Uri
import org.http4k.core.then
import org.http4k.filter.ClientFilters
import org.http4k.filter.ClientFilters.SetXForwardedHost

/**
* Standard HTTP implementation of OIDC
*/
fun OIDC.Companion.Http(
region: Region,
http: HttpHandler = JavaHttpClient(),
) = object : OIDC {
private val routedHttp = ClientFilters.SetHostFrom(Uri.of("https://oidc.$region.amazonaws.com"))
.then(SetXForwardedHost())
.then(http)

override fun <R : Any> invoke(action: OIDCAction<R>) = action.toResult(routedHttp(action.toRequest()))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.http4k.connect.amazon.iamidentitycenter

import org.http4k.client.JavaHttpClient
import org.http4k.connect.amazon.core.model.Region
import org.http4k.core.HttpHandler
import org.http4k.core.Uri
import org.http4k.core.then
import org.http4k.filter.ClientFilters

/**
* Standard HTTP implementation of SSO
*/
fun SSO.Companion.Http(
region: Region,
http: HttpHandler = JavaHttpClient(),
) = object : SSO {

private val routedHttp = ClientFilters.SetHostFrom(Uri.of("https://portal.sso.$region.amazonaws.com"))
.then(ClientFilters.SetXForwardedHost())
.then(http)

override fun <R : Any> invoke(action: SSOAction<R>) = action.toResult(routedHttp(action.toRequest()))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.http4k.connect.amazon.iamidentitycenter

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import org.http4k.connect.amazon.iamidentitycenter.model.AccessToken
import org.http4k.connect.amazon.iamidentitycenter.model.ClientId
import org.http4k.connect.amazon.iamidentitycenter.model.ClientName
import org.http4k.connect.amazon.iamidentitycenter.model.ClientSecret
import org.http4k.connect.amazon.iamidentitycenter.model.DeviceCode
import org.http4k.connect.amazon.iamidentitycenter.model.IdToken
import org.http4k.connect.amazon.iamidentitycenter.model.RefreshToken
import org.http4k.connect.amazon.iamidentitycenter.model.RoleName
import org.http4k.connect.amazon.iamidentitycenter.model.SessionId
import org.http4k.connect.amazon.iamidentitycenter.model.UserCode
import org.http4k.format.AwsCoreJsonAdapterFactory
import org.http4k.format.ConfigurableMoshi
import org.http4k.format.ListAdapter
import org.http4k.format.MapAdapter
import org.http4k.format.asConfigurable
import org.http4k.format.value
import org.http4k.format.withAwsCoreMappings
import org.http4k.format.withStandardMappings
import se.ansman.kotshi.KotshiJsonAdapterFactory

object IAMIdentityCenterMoshi : ConfigurableMoshi(
Moshi.Builder()
.add(IAMIdentityCenterJsonAdapterFactory)
.add(AwsCoreJsonAdapterFactory())
.add(ListAdapter)
.add(MapAdapter)
.asConfigurable()
.withStandardMappings()
.value(AccessToken)
.value(ClientName)
.value(ClientId)
.value(ClientSecret)
.value(DeviceCode)
.value(IdToken)
.value(RefreshToken)
.value(SessionId)
.value(RoleName)
.value(UserCode)
.withAwsCoreMappings()
.done()
)

@KotshiJsonAdapterFactory
object IAMIdentityCenterJsonAdapterFactory : JsonAdapter.Factory by KotshiIAMIdentityCenterJsonAdapterFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.http4k.connect.amazon.iamidentitycenter

import dev.forkhandles.result4k.Result
import org.http4k.connect.Http4kConnectAdapter
import org.http4k.connect.RemoteFailure
import org.http4k.connect.amazon.AwsServiceCompanion

/**
* Docs: https://docs.aws.amazon.com/singlesignon/latest/OIDCAPIReference/Welcome.html
*/
@Http4kConnectAdapter
interface OIDC {
operator fun <R : Any> invoke(action: OIDCAction<R>): Result<R, RemoteFailure>

companion object : AwsServiceCompanion("oidc")
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.http4k.connect.amazon.iamidentitycenter

import org.http4k.connect.NonNullAutoMarshalledAction
import kotlin.reflect.KClass

abstract class OIDCAction<R : Any>(clazz: KClass<R>) : NonNullAutoMarshalledAction<R>(clazz, IAMIdentityCenterMoshi)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.http4k.connect.amazon.iamidentitycenter

import dev.forkhandles.result4k.Result
import org.http4k.connect.Http4kConnectAdapter
import org.http4k.connect.RemoteFailure
import org.http4k.connect.amazon.AwsServiceCompanion

@Http4kConnectAdapter
interface SSO {
operator fun <R : Any> invoke(action: SSOAction<R>): Result<R, RemoteFailure>

companion object : AwsServiceCompanion("sso")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.http4k.connect.amazon.iamidentitycenter

import org.http4k.connect.NonNullAutoMarshalledAction
import kotlin.reflect.KClass

abstract class SSOAction<R : Any>(clazz: KClass<R>) : NonNullAutoMarshalledAction<R>(clazz, IAMIdentityCenterMoshi)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class AccessToken private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<AccessToken>(::AccessToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class ClientId private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<ClientId>(::ClientId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class ClientName private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<ClientName>(::ClientName)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class ClientSecret private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<ClientSecret>(::ClientSecret)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class DeviceCode private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<DeviceCode>(::DeviceCode)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class IdToken private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<IdToken>(::IdToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class RefreshToken private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<RefreshToken>(::RefreshToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class RoleName private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<RoleName>(::RoleName)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import org.http4k.connect.amazon.core.model.AwsAccount
import org.http4k.connect.amazon.core.model.Region
import org.http4k.core.Uri

data class SSOProfile(
val accountId: AwsAccount,
val roleName: RoleName,
val region: Region,
val startUri: Uri
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class SessionId private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<SessionId>(::SessionId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.http4k.connect.amazon.iamidentitycenter.model

import dev.forkhandles.values.NonBlankStringValueFactory
import dev.forkhandles.values.StringValue

class UserCode private constructor(value: String) : StringValue(value) {
companion object : NonBlankStringValueFactory<UserCode>(::UserCode)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.http4k.connect.amazon.iamidentitycenter.oidc.action

import org.http4k.connect.Http4kConnectAction
import org.http4k.connect.amazon.iamidentitycenter.IAMIdentityCenterMoshi
import org.http4k.connect.amazon.iamidentitycenter.OIDCAction
import org.http4k.connect.amazon.iamidentitycenter.model.AccessToken
import org.http4k.connect.amazon.iamidentitycenter.model.ClientId
import org.http4k.connect.amazon.iamidentitycenter.model.ClientSecret
import org.http4k.connect.amazon.iamidentitycenter.model.DeviceCode
import org.http4k.connect.amazon.iamidentitycenter.model.IdToken
import org.http4k.connect.amazon.iamidentitycenter.model.RefreshToken
import org.http4k.connect.amazon.iamidentitycenter.model.SessionId
import org.http4k.connect.kClass
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.with
import se.ansman.kotshi.JsonSerializable

@Http4kConnectAction
data class CreateToken(
val clientId: ClientId,
val clientSecret: ClientSecret,
val deviceCode: DeviceCode,
) : OIDCAction<DeviceToken>(kClass()) {
override fun toRequest() = Request(Method.POST, "token")
.with(
IAMIdentityCenterMoshi.autoBody<Any>().toLens() of mapOf(
"clientId" to clientId,
"clientSecret" to clientSecret,
"deviceCode" to deviceCode,
"grantType" to "urn:ietf:params:oauth:grant-type:device_code"
)
)
}


@JsonSerializable
data class DeviceToken(
val accessToken: AccessToken,
val expiresIn: Long,
val idToken: IdToken?,
val refreshToken: RefreshToken?,
val aws_sso_app_session_id: SessionId?,
val originSessionId: SessionId?,
val issuedTokenType: String?,
val tokenType: String,
)
Loading

0 comments on commit 35237d7

Please sign in to comment.