-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support binding OAuth2 user automatically
Signed-off-by: JohnNiang <[email protected]>
- Loading branch information
Showing
36 changed files
with
889 additions
and
136 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
14 changes: 14 additions & 0 deletions
14
api/src/main/java/run/halo/app/security/HttpBasicSecurityWebFilter.java
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 run.halo.app.security; | ||
|
||
import org.pf4j.ExtensionPoint; | ||
import org.springframework.web.server.WebFilter; | ||
|
||
/** | ||
* Security web filter for HTTP basic. | ||
* | ||
* @author johnniang | ||
* @since 2.20.0 | ||
*/ | ||
public interface HttpBasicSecurityWebFilter extends WebFilter, ExtensionPoint { | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
api/src/main/java/run/halo/app/security/OAuth2AuthorizationCodeSecurityWebFilter.java
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 run.halo.app.security; | ||
|
||
import org.pf4j.ExtensionPoint; | ||
import org.springframework.web.server.WebFilter; | ||
|
||
/** | ||
* Security web filter for OAuth2 authorization code. | ||
* | ||
* @author johnniang | ||
* @since 2.20.0 | ||
*/ | ||
public interface OAuth2AuthorizationCodeSecurityWebFilter extends WebFilter, ExtensionPoint { | ||
|
||
} |
96 changes: 96 additions & 0 deletions
96
.../main/java/run/halo/app/security/authentication/oauth2/HaloOAuth2AuthenticationToken.java
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,96 @@ | ||
package run.halo.app.security.authentication.oauth2; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import lombok.Getter; | ||
import org.springframework.security.authentication.AbstractAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | ||
import org.springframework.security.oauth2.core.user.OAuth2User; | ||
|
||
/** | ||
* Halo OAuth2 authentication token which combines {@link UserDetails} and original | ||
* {@link OAuth2AuthenticationToken}. | ||
* | ||
* @author johnniang | ||
* @since 2.20.0 | ||
*/ | ||
public class HaloOAuth2AuthenticationToken extends AbstractAuthenticationToken { | ||
|
||
@Getter | ||
private final UserDetails userDetails; | ||
|
||
@Getter | ||
private final OAuth2AuthenticationToken original; | ||
|
||
/** | ||
* Constructs an {@code HaloOAuth2AuthenticationToken} using {@link UserDetails} and original | ||
* {@link OAuth2AuthenticationToken}. | ||
* | ||
* @param userDetails the {@link UserDetails} | ||
* @param original the original {@link OAuth2AuthenticationToken} | ||
*/ | ||
public HaloOAuth2AuthenticationToken(UserDetails userDetails, | ||
OAuth2AuthenticationToken original) { | ||
super(combineAuthorities(userDetails, original)); | ||
this.userDetails = userDetails; | ||
this.original = original; | ||
setAuthenticated(true); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return userDetails.getUsername(); | ||
} | ||
|
||
@Override | ||
public Collection<GrantedAuthority> getAuthorities() { | ||
var originalAuthorities = super.getAuthorities(); | ||
var userDetailsAuthorities = getUserDetails().getAuthorities(); | ||
var authorities = new ArrayList<GrantedAuthority>( | ||
originalAuthorities.size() + userDetailsAuthorities.size() | ||
); | ||
authorities.addAll(originalAuthorities); | ||
authorities.addAll(userDetailsAuthorities); | ||
return Collections.unmodifiableList(authorities); | ||
} | ||
|
||
@Override | ||
public Object getCredentials() { | ||
return ""; | ||
} | ||
|
||
@Override | ||
public OAuth2User getPrincipal() { | ||
return original.getPrincipal(); | ||
} | ||
|
||
/** | ||
* Creates an authenticated {@link HaloOAuth2AuthenticationToken} using {@link UserDetails} and | ||
* original {@link OAuth2AuthenticationToken}. | ||
* | ||
* @param userDetails the {@link UserDetails} | ||
* @param original the original {@link OAuth2AuthenticationToken} | ||
* @return an authenticated {@link HaloOAuth2AuthenticationToken} | ||
*/ | ||
public static HaloOAuth2AuthenticationToken authenticated( | ||
UserDetails userDetails, OAuth2AuthenticationToken original | ||
) { | ||
return new HaloOAuth2AuthenticationToken(userDetails, original); | ||
} | ||
|
||
private static Collection<? extends GrantedAuthority> combineAuthorities( | ||
UserDetails userDetails, OAuth2AuthenticationToken original) { | ||
var userDetailsAuthorities = userDetails.getAuthorities(); | ||
var originalAuthorities = original.getAuthorities(); | ||
var authorities = new ArrayList<GrantedAuthority>( | ||
originalAuthorities.size() + userDetailsAuthorities.size() | ||
); | ||
authorities.addAll(originalAuthorities); | ||
authorities.addAll(userDetailsAuthorities); | ||
return Collections.unmodifiableList(authorities); | ||
} | ||
|
||
} |
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
35 changes: 35 additions & 0 deletions
35
application/src/main/java/run/halo/app/core/user/service/UserConnectionService.java
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,35 @@ | ||
package run.halo.app.core.user.service; | ||
|
||
import org.springframework.security.oauth2.core.user.OAuth2User; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.core.extension.UserConnection; | ||
|
||
public interface UserConnectionService { | ||
|
||
/** | ||
* Create user connection. | ||
* | ||
* @param username Username | ||
* @param registrationId Registration id | ||
* @param oauth2User OAuth2 user | ||
* @return Created user connection | ||
*/ | ||
Mono<UserConnection> createUserConnection( | ||
String username, | ||
String registrationId, | ||
OAuth2User oauth2User | ||
); | ||
|
||
/** | ||
* Get user connection by registration id and OAuth2 user. | ||
* If found, update updatedAt timestamp of the user connection. | ||
* | ||
* @param registrationId Registration id | ||
* @param oauth2User OAuth2 user | ||
* @return Updated user connection or empty | ||
*/ | ||
Mono<UserConnection> getUserConnection( | ||
String registrationId, OAuth2User oauth2User | ||
); | ||
|
||
} |
86 changes: 86 additions & 0 deletions
86
application/src/main/java/run/halo/app/core/user/service/impl/UserConnectionServiceImpl.java
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,86 @@ | ||
package run.halo.app.core.user.service.impl; | ||
|
||
import static run.halo.app.extension.ExtensionUtil.defaultSort; | ||
import static run.halo.app.extension.index.query.QueryFactory.and; | ||
import static run.halo.app.extension.index.query.QueryFactory.equal; | ||
|
||
import java.time.Clock; | ||
import java.util.HashMap; | ||
import java.util.Optional; | ||
import org.springframework.security.oauth2.core.user.OAuth2User; | ||
import org.springframework.stereotype.Service; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.core.extension.UserConnection; | ||
import run.halo.app.core.extension.UserConnection.UserConnectionSpec; | ||
import run.halo.app.core.user.service.UserConnectionService; | ||
import run.halo.app.extension.ListOptions; | ||
import run.halo.app.extension.Metadata; | ||
import run.halo.app.extension.MetadataOperator; | ||
import run.halo.app.extension.ReactiveExtensionClient; | ||
import run.halo.app.infra.utils.JsonUtils; | ||
|
||
@Service | ||
public class UserConnectionServiceImpl implements UserConnectionService { | ||
|
||
private final ReactiveExtensionClient client; | ||
|
||
private Clock clock = Clock.systemDefaultZone(); | ||
|
||
public UserConnectionServiceImpl(ReactiveExtensionClient client) { | ||
this.client = client; | ||
} | ||
|
||
void setClock(Clock clock) { | ||
this.clock = clock; | ||
} | ||
|
||
@Override | ||
public Mono<UserConnection> createUserConnection( | ||
String username, | ||
String registrationId, | ||
OAuth2User oauth2User | ||
) { | ||
var connection = new UserConnection(); | ||
connection.setMetadata(new Metadata()); | ||
var metadata = connection.getMetadata(); | ||
updateUserInfo(metadata, oauth2User); | ||
metadata.setGenerateName(username + "-"); | ||
connection.setSpec(new UserConnectionSpec()); | ||
var spec = connection.getSpec(); | ||
spec.setUsername(username); | ||
spec.setProviderUserId(oauth2User.getName()); | ||
spec.setRegistrationId(registrationId); | ||
spec.setUpdatedAt(clock.instant()); | ||
return client.create(connection); | ||
} | ||
|
||
private Mono<UserConnection> updateUserConnection(UserConnection connection, | ||
OAuth2User oauth2User) { | ||
connection.getSpec().setUpdatedAt(clock.instant()); | ||
updateUserInfo(connection.getMetadata(), oauth2User); | ||
return client.update(connection); | ||
} | ||
|
||
@Override | ||
public Mono<UserConnection> getUserConnection(String registrationId, | ||
OAuth2User oauth2User) { | ||
var listOptions = ListOptions.builder() | ||
.fieldQuery(and( | ||
equal("spec.registrationId", registrationId), | ||
equal("spec.providerUserId", oauth2User.getName()) | ||
)) | ||
.build(); | ||
return client.listAll(UserConnection.class, listOptions, defaultSort()).next() | ||
.flatMap(connection -> updateUserConnection(connection, oauth2User)); | ||
} | ||
|
||
private void updateUserInfo(MetadataOperator metadata, OAuth2User oauth2User) { | ||
var annotations = Optional.ofNullable(metadata.getAnnotations()) | ||
.orElseGet(HashMap::new); | ||
metadata.setAnnotations(annotations); | ||
annotations.put( | ||
"auth.halo.run/oauth2-user-info", | ||
JsonUtils.objectToJson(oauth2User.getAttributes()) | ||
); | ||
} | ||
} |
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
Oops, something went wrong.