Skip to content

Commit

Permalink
[FEATURE] Added Spring Profile Management (#74)
Browse files Browse the repository at this point in the history
Added new profile called 'local' to run the app isolated from other services.

On local profile:

no external system is required for authentication(a set of access tokens is generated automatically)
no external system is required for loading a playable items(set of playable items generated automatically)
  • Loading branch information
justJavaProgrammer authored Aug 21, 2024
1 parent cacea72 commit 194450e
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.odeyalo.sonata.connect.config.profiles.local;

import com.odeyalo.suite.security.auth.TokenAuthenticationManager;
import com.odeyalo.suite.security.auth.token.AccessTokenMetadata;
import com.odeyalo.suite.security.auth.token.ReactiveAccessTokenValidator;
import com.odeyalo.suite.security.auth.token.ValidatedAccessToken;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.Map;

@Configuration
@Profile("local")
public class AuthenticationConfiguration {

@Bean
@Primary
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
return new TokenAuthenticationManager(
new LocalDevelopmentAccessTokenValidator()
);
}

private static class LocalDevelopmentAccessTokenValidator implements ReactiveAccessTokenValidator {
private final Map<String, ValidatedAccessToken> tokensCache = Map.of(
"token1", ValidatedAccessToken.valid(
AccessTokenMetadata.of("123", new String[]{"read", "write"},
Instant.now().getEpochSecond(),
Instant.now().plusSeconds(600).getEpochSecond()
)),
"token1_2", ValidatedAccessToken.valid(
AccessTokenMetadata.of("123", new String[]{"read", "write", "playlist"},
Instant.now().getEpochSecond(),
Instant.now().plusSeconds(600).getEpochSecond()
)),
"token2", ValidatedAccessToken.valid(
AccessTokenMetadata.of("miku", new String[]{"read", "write", "playlist"},
Instant.now().getEpochSecond(),
Instant.now().plusSeconds(600).getEpochSecond()
))
);

private final Logger logger = LoggerFactory.getLogger(LocalDevelopmentAccessTokenValidator.class);

public LocalDevelopmentAccessTokenValidator() {
logger.info("Using local 'DEV' mode, cache of available access tokens to use. Token that is not exist in cache will cause HTTP 401 UNAUTHORIZED status");

tokensCache.forEach((key, value) -> {
final AccessTokenMetadata tokenMetadata = value.getToken();
logger.info("Generated access token: '{}' for user 'ID({})' with following scopes: '({})', expire after: {} seconds", key, tokenMetadata.getUserId(), tokenMetadata.getScopes(), tokenMetadata.getExpiresIn() - Instant.now().getEpochSecond());
});

}

@Override
@NotNull
public Mono<ValidatedAccessToken> validateToken(@NotNull final String tokenValue) {

final ValidatedAccessToken validatedAccessToken = tokensCache.getOrDefault(tokenValue, ValidatedAccessToken.invalid());

return Mono.just(validatedAccessToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.odeyalo.sonata.connect.config.profiles.local;

import com.odeyalo.sonata.common.context.ContextUri;
import com.odeyalo.sonata.connect.model.*;
import com.odeyalo.sonata.connect.model.track.*;
import com.odeyalo.sonata.connect.service.player.support.PlayableItemLoader;
import com.odeyalo.sonata.connect.service.player.support.PredefinedPlayableItemLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import java.net.URI;
import java.util.List;

@Configuration
@Profile("local")
public class PlayableItemsConfig {
private final Logger logger = LoggerFactory.getLogger(PlayableItemsConfig.class);

@Bean
public PlayableItemLoader playableItemLoader() {
ArtistList artists = ArtistList.solo(
Artist.of(ArtistSpec.ArtistId.of("fea3aFas3f"), "Alex G", ContextUri.forArtist("fea3aFas3f"))
);

ImageList images = ImageList.builder()
.image(Image.builder().url(URI.create("https://i.pinimg.com/564x/02/27/b0/0227b0ff5ff93d6429d2c80d402cea43.jpg")).build())
.image(Image.builder().url(URI.create("https://i.pinimg.com/564x/db/ff/9f/dbff9f74ef082687010dacc455eac7ac.jpg")).build())
.build();

Album albumInfo = Album.builder()
.id(AlbumSpec.AlbumId.of("a3la23bu91m"))
.artists(artists)
.totalTrackCount(2)
.albumType(AlbumSpec.AlbumType.EPISODE)
.name("Sarah")
.images(images)
.build();

var item = TrackItem.builder()
.id("04nJixim5a0MAz3PGiVID1")
.contextUri(ContextUri.forTrack("04nJixim5a0MAz3PGiVID1"))
.name("Something")
.explicit(true)
.duration(PlayableItemDuration.ofMilliseconds(790024))
.artists(artists)
.order(TrackItemSpec.Order.of(1, 1))
.album(albumInfo)
.build();

ImageList images2 = ImageList.builder()
.image(Image.builder().url(URI.create("https://i.pinimg.com/564x/90/bc/a8/90bca83aa94a664206a7e4c305888023.jpg")).build())
.build();


Album albumInfo2 = Album.builder()
.id(AlbumSpec.AlbumId.of("miku123"))
.artists(artists)
.totalTrackCount(2)
.albumType(AlbumSpec.AlbumType.EPISODE)
.name("Sarah")
.images(images2)
.build();

var item2 = TrackItem.builder()
.id("miku123")
.contextUri(ContextUri.forTrack("miku123"))
.name("Something")
.explicit(true)
.duration(PlayableItemDuration.ofMilliseconds(790024))
.artists(artists)
.order(TrackItemSpec.Order.of(1, 1))
.album(albumInfo2)
.build();

logger.info("Created new playable items for local dev only with ids: {}, {}", item.getId(), item2.getId());

return new PredefinedPlayableItemLoader(List.of(
item, item2
));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.odeyalo.sonata.connect.config.security.configurer;

import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
import org.springframework.stereotype.Component;
Expand All @@ -8,13 +9,14 @@
* Helper class to configure the {@link AuthorizeExchangeSpec}
*/
@Component
public class AuthorizeExchangeSpecConfigurer implements Customizer<AuthorizeExchangeSpec> {
public final class AuthorizeExchangeSpecConfigurer implements Customizer<AuthorizeExchangeSpec> {
private static final String SCA_TOKEN_EXCHANGE_ENDPOINT = "/connect/auth/exchange**";

@Override
public void customize(AuthorizeExchangeSpec authorizeExchangeSpec) {
authorizeExchangeSpec
.pathMatchers(SCA_TOKEN_EXCHANGE_ENDPOINT).permitAll()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().authenticated();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.springframework.security.config.Customizer;
import org.springframework.security.config.web.server.ServerHttpSecurity.CorsSpec;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
* Used to configure the {@link CorsSpec}
Expand All @@ -12,6 +14,12 @@ public class CorsSpecConfigurer implements Customizer<CorsSpec> {

@Override
public void customize(CorsSpec corsSpec) {
corsSpec.disable();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.applyPermitDefaultValues();
source.registerCorsConfiguration("/**", config);

corsSpec.configurationSource(source);
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application-local.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

spring.webflux.base-path=/v1

eureka.client.enabled=false

0 comments on commit 194450e

Please sign in to comment.