-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature/Identity] Adds Basic Auth mechanism via Internal IdP #4798
Changes from all commits
eae97da
283ec5c
68fb702
fe5a468
2aaccf0
a154fdb
047bbea
306f590
4a00f72
b57d7b2
6d78226
3d315df
69d6acd
ad58ff5
b87adb7
2134f73
430cc9b
c652fa2
f29ce16
ad66c3a
77c543e
78e4ac1
bfb6e6a
abf05d3
553788d
5ae8043
bb97c92
faae7f7
a2e68bf
27422fe
d09309c
7597e5b
c529707
b988ae2
96c28ca
d87f401
37c51a9
eee71cc
a1ae88a
ac4aab4
9dc87b1
1403ad2
ba32681
ae3ee0b
d6bed20
a34fcc9
2b0b7df
554aad6
41470d9
f22287d
8079479
044317b
109ee9b
cf127a7
927be7f
eddd2a2
6b1cf9d
dbfbcb3
c14ab0c
1ab5c96
4b0aa3a
5ac1c09
5faad80
aac8bfd
4848e59
a747867
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.authn; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.apache.shiro.authc.AuthenticationToken; | ||
import org.apache.shiro.authc.UsernamePasswordToken; | ||
import org.opensearch.authn.tokens.BasicAuthToken; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
|
||
/** | ||
* Extracts Shiro's {@link AuthenticationToken} from different types of auth headers | ||
* | ||
* @opensearch.experimental | ||
*/ | ||
public class AuthenticationTokenHandler { | ||
|
||
private static final Logger logger = LogManager.getLogger(AuthenticationTokenHandler.class); | ||
|
||
/** | ||
* Extracts shiro auth token from the given header token | ||
* @param authenticationToken the token from which to extract | ||
* @return the extracted shiro auth token to be used to perform login | ||
*/ | ||
public static AuthenticationToken extractShiroAuthToken(org.opensearch.authn.tokens.AuthenticationToken authenticationToken) { | ||
AuthenticationToken authToken = null; | ||
|
||
if (authenticationToken instanceof BasicAuthToken) { | ||
authToken = handleBasicAuth((BasicAuthToken) authenticationToken); | ||
} | ||
// TODO: check for other type of HeaderTokens | ||
return authToken; | ||
} | ||
|
||
/** | ||
* Returns auth token extracted from basic auth header | ||
* @param token the basic auth token | ||
* @return the extracted auth token | ||
*/ | ||
private static AuthenticationToken handleBasicAuth(final BasicAuthToken token) { | ||
|
||
final byte[] decodedAuthHeader = Base64.getDecoder().decode(token.getHeaderValue().substring("Basic".length()).trim()); | ||
String decodedHeader = new String(decodedAuthHeader, StandardCharsets.UTF_8); | ||
|
||
final int firstColonIndex = decodedHeader.indexOf(':'); | ||
|
||
String username = null; | ||
String password = null; | ||
|
||
if (firstColonIndex > 0) { | ||
username = decodedHeader.substring(0, firstColonIndex); | ||
|
||
if (decodedHeader.length() - 1 != firstColonIndex) { | ||
password = decodedHeader.substring(firstColonIndex + 1); | ||
} else { | ||
// blank password | ||
password = ""; | ||
} | ||
} | ||
|
||
if (username == null || password == null) { | ||
logger.warn("Invalid 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); | ||
return null; | ||
} | ||
|
||
logger.info("Logging in as: " + username); | ||
|
||
return new UsernamePasswordToken(username, password); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,9 @@ | |
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.InjectableValues; | ||
import com.fasterxml.jackson.databind.MapperFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.json.JsonMapper; | ||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | ||
|
||
import java.io.IOException; | ||
|
@@ -18,14 +20,18 @@ | |
* @opensearch.experimental | ||
*/ | ||
public class DefaultObjectMapper { | ||
public static final ObjectMapper objectMapper = new ObjectMapper(); | ||
public static ObjectMapper objectMapper; | ||
public final static ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); | ||
private static final ObjectMapper defaulOmittingObjectMapper = new ObjectMapper(); | ||
|
||
static { | ||
objectMapper = JsonMapper.builder() | ||
.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION) | ||
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS) // to prevent access denied exception by Jackson | ||
.build(); | ||
|
||
objectMapper.setSerializationInclusion(Include.NON_NULL); | ||
// objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); | ||
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. enable/ disable are deprecated and JsonMapper.builder() is suggested to be used for building a mapper |
||
defaulOmittingObjectMapper.setSerializationInclusion(Include.NON_DEFAULT); | ||
defaulOmittingObjectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); | ||
YAML_MAPPER.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,13 +10,21 @@ | |
import java.util.Collections; | ||
import java.util.Map; | ||
|
||
/** | ||
* A non-volatile and immutable object in the storage. | ||
* | ||
* @opensearch.experimental | ||
*/ | ||
|
||
public class User { | ||
|
||
@JsonProperty(value = "primary_principal") | ||
private StringPrincipal primaryPrincipal; | ||
|
||
@JsonProperty(value = "hash") | ||
private String bcryptHash; | ||
|
||
@JsonProperty(value = "attributes") | ||
private Map<String, String> attributes = Collections.emptyMap(); | ||
|
||
@JsonProperty(value = "primary_principal") | ||
|
@@ -39,10 +47,12 @@ public void setBcryptHash(String bcryptHash) { | |
this.bcryptHash = bcryptHash; | ||
} | ||
|
||
@JsonProperty(value = "attributes") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really a review comment just curious though, what does this syntax denote? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When serializing/deserializing a JSON object from/to a class, the mapper will know which field to map against when it see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh gotcha. Thank you for clearing that up. Overall this looks great! |
||
public Map<String, String> getAttributes() { | ||
return attributes; | ||
} | ||
|
||
@JsonProperty(value = "attributes") | ||
public void setAttributes(Map<String, String> attributes) { | ||
this.attributes = attributes; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.opensearch.authn.internal; | ||
|
||
import org.opensearch.authn.AccessTokenManager; | ||
import org.opensearch.authn.tokens.AccessToken; | ||
|
||
/** | ||
* Implementation of access token manager that does not enforce authentication | ||
* | ||
* This class and related classes in this package will not return nulls or fail permissions checks | ||
* | ||
* @opensearch.internal | ||
*/ | ||
public class InternalAccessTokenManager implements AccessTokenManager { | ||
|
||
@Override | ||
public void expireAllTokens() { | ||
// Tokens cannot be expired | ||
} | ||
|
||
@Override | ||
public AccessToken generate() { | ||
return new AccessToken(); | ||
} | ||
|
||
@Override | ||
public AccessToken refresh(final AccessToken token) { | ||
return new AccessToken(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.authn.internal; | ||
|
||
import org.opensearch.authn.AccessTokenManager; | ||
import org.opensearch.authn.AuthenticationManager; | ||
import org.opensearch.authn.realm.InternalRealm; | ||
import org.opensearch.authn.Subject; | ||
import org.apache.shiro.SecurityUtils; | ||
import org.apache.shiro.mgt.DefaultSecurityManager; | ||
import org.apache.shiro.mgt.SecurityManager; | ||
|
||
/** | ||
* Implementation of authentication manager that enforces authentication against internal idp | ||
* | ||
* This class and related classes in this package will not return nulls or fail permissions checks | ||
* | ||
* This class manages the subjects loaded via the realm, and provides current subject | ||
* when authenticating the incoming request | ||
* Checkout | ||
* and how the internal Identity system uses auth manager to get current subject to use for authentication | ||
* | ||
* @opensearch.internal | ||
*/ | ||
public class InternalAuthenticationManager implements AuthenticationManager { | ||
|
||
/** | ||
* Security manager is loaded with default user set, | ||
* and this instantiation uses the default security manager | ||
*/ | ||
public InternalAuthenticationManager() { | ||
final SecurityManager securityManager = new DefaultSecurityManager(InternalRealm.INSTANCE); | ||
SecurityUtils.setSecurityManager(securityManager); | ||
} | ||
|
||
/** | ||
* Instantiates this Auth manager by setting the custom security Manager that is passed as an argument | ||
* @param securityManager the custom security manager (with realm instantiated in it) | ||
*/ | ||
public InternalAuthenticationManager(SecurityManager securityManager) { | ||
SecurityUtils.setSecurityManager(securityManager); | ||
} | ||
|
||
@Override | ||
public Subject getSubject() { | ||
return new InternalSubject(SecurityUtils.getSubject()); | ||
} | ||
|
||
@Override | ||
public AccessTokenManager getAccessTokenManager() { | ||
return null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will let you access internal dependencies of shiro, is that the intention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this change is needed, Could you provide the why of switching from
implementation
toapi
dependency type?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to set it as
api
otherwise it throwserror: package org.apache.shiro.authc does not exist
inside RestController.java#L38For core to be able to use Shiro module imported via authn package, shiro package needs to be public (i.e.
api
). If it is changed toimplementation
shiro import becomes private and we will have to import it separately inserver/build.gradle
to use it.If I change it back to
implementation
here and add a line server/build.gradle likeimplementation 'org.apache.shiro:shiro-core:1.9.1'
the gradle works fine.IMO there is no need to import Shiro twice as it is not a big build performance issue in using
api
vsimplementation
here.I found this helpful: https://stackoverflow.com/questions/44413952/gradle-implementation-vs-api-configuration