Make sure JAX-RS resource methods are advertizing the media type they support
Closes #15811 Closes #15810
This commit is contained in:
parent
2f0d8cd895
commit
022d2864a6
21 changed files with 210 additions and 55 deletions
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.http;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface FormPartValue {
|
||||||
|
|
||||||
|
String asString();
|
||||||
|
InputStream asInputStream();
|
||||||
|
|
||||||
|
}
|
|
@ -47,6 +47,13 @@ public interface HttpRequest {
|
||||||
*/
|
*/
|
||||||
MultivaluedMap<String, String> getDecodedFormParameters();
|
MultivaluedMap<String, String> getDecodedFormParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the parts from a multipart form request (e.g.: multipart/form-data media type).
|
||||||
|
*
|
||||||
|
* @return the parts from a multipart form request
|
||||||
|
*/
|
||||||
|
MultivaluedMap<String, FormPartValue> getMultiPartFormParameters();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the HTTP headers.
|
* Returns the HTTP headers.
|
||||||
*
|
*
|
||||||
|
|
|
@ -136,7 +136,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity) {
|
public Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity) {
|
||||||
return Response.ok(identity.getToken()).build();
|
return Response.ok(identity.getToken()).type(MediaType.APPLICATION_JSON).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -254,7 +254,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity) {
|
public Response retrieveToken(KeycloakSession session, FederatedIdentityModel identity) {
|
||||||
return Response.ok(identity.getToken()).build();
|
return Response.ok(identity.getToken()).type(MediaType.TEXT_PLAIN_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,6 +33,8 @@ import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||||
import org.keycloak.services.clientpolicy.context.TokenIntrospectContext;
|
import org.keycloak.services.clientpolicy.context.TokenIntrospectContext;
|
||||||
|
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
@ -66,6 +68,7 @@ public class TokenIntrospectionEndpoint {
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@NoCache
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response introspect() {
|
public Response introspect() {
|
||||||
event.event(EventType.INTROSPECT_TOKEN);
|
event.event(EventType.INTROSPECT_TOKEN);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.OPTIONS;
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -83,6 +84,7 @@ public class TokenRevocationEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response revoke() {
|
public Response revoke() {
|
||||||
event.event(EventType.REVOKE_GRANT);
|
event.event(EventType.REVOKE_GRANT);
|
||||||
|
|
|
@ -73,6 +73,7 @@ import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.OPTIONS;
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -119,6 +120,7 @@ public class UserInfoEndpoint {
|
||||||
@Path("/")
|
@Path("/")
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
|
@Produces(javax.ws.rs.core.MediaType.APPLICATION_JSON)
|
||||||
public Response issueUserInfoGet() {
|
public Response issueUserInfoGet() {
|
||||||
setupCors();
|
setupCors();
|
||||||
String accessToken = this.appAuthManager.extractAuthorizationHeaderTokenOrReturnNull(session.getContext().getRequestHeaders());
|
String accessToken = this.appAuthManager.extractAuthorizationHeaderTokenOrReturnNull(session.getContext().getRequestHeaders());
|
||||||
|
@ -129,6 +131,7 @@ public class UserInfoEndpoint {
|
||||||
@Path("/")
|
@Path("/")
|
||||||
@POST
|
@POST
|
||||||
@NoCache
|
@NoCache
|
||||||
|
@Produces(javax.ws.rs.core.MediaType.APPLICATION_JSON)
|
||||||
public Response issueUserInfoPost() {
|
public Response issueUserInfoPost() {
|
||||||
setupCors();
|
setupCors();
|
||||||
|
|
||||||
|
|
|
@ -1425,7 +1425,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
EntityUtils.consumeQuietly(result.getEntity());
|
EntityUtils.consumeQuietly(result.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException | ProcessingException | ParsingException e) {
|
} catch (IOException | ProcessingException | ParsingException | IllegalArgumentException e) {
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
event.detail(Details.REASON, e.getMessage());
|
event.detail(Details.REASON, e.getMessage());
|
||||||
event.error(Errors.IDENTITY_PROVIDER_ERROR);
|
event.error(Errors.IDENTITY_PROVIDER_ERROR);
|
||||||
|
|
|
@ -76,8 +76,7 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
||||||
uriInfo = new HashMap<>();
|
uriInfo = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
UriInfo originalUriInfo = getContextObject(UriInfo.class);
|
uriInfo.put(type, new KeycloakUriInfo(session, type, getHttpRequest().getUri()));
|
||||||
uriInfo.put(type, new KeycloakUriInfo(session, type, originalUriInfo));
|
|
||||||
}
|
}
|
||||||
return uriInfo.get(type);
|
return uriInfo.get(type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.services;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import org.keycloak.http.FormPartValue;
|
||||||
|
|
||||||
|
public class FormPartValueImpl implements FormPartValue {
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private InputStream inputStream;
|
||||||
|
|
||||||
|
public FormPartValueImpl(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormPartValueImpl(InputStream inputStream) {
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String asString() {
|
||||||
|
if (inputStream != null) {
|
||||||
|
throw new RuntimeException("Value is a input stream");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream asInputStream() {
|
||||||
|
if (inputStream == null) {
|
||||||
|
return new ByteArrayInputStream(value.getBytes());
|
||||||
|
}
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,26 @@
|
||||||
|
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
import org.keycloak.http.HttpRequest;
|
import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE;
|
||||||
|
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.security.cert.X509Certificate;
|
import javax.ws.rs.ext.MessageBodyReader;
|
||||||
|
import javax.ws.rs.ext.Providers;
|
||||||
|
import org.jboss.resteasy.core.ResteasyContext;
|
||||||
|
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
||||||
|
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
||||||
|
import org.keycloak.http.FormPartValue;
|
||||||
|
import org.keycloak.http.HttpRequest;
|
||||||
|
|
||||||
public class HttpRequestImpl implements HttpRequest {
|
public class HttpRequestImpl implements HttpRequest {
|
||||||
|
|
||||||
|
@ -48,6 +62,41 @@ public class HttpRequestImpl implements HttpRequest {
|
||||||
return delegate.getDecodedFormParameters();
|
return delegate.getDecodedFormParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultivaluedMap<String, FormPartValue> getMultiPartFormParameters() {
|
||||||
|
try {
|
||||||
|
MediaType mediaType = getHttpHeaders().getMediaType();
|
||||||
|
|
||||||
|
if (!MULTIPART_FORM_DATA_TYPE.isCompatible(mediaType) || !mediaType.getParameters().containsKey("boundary")) {
|
||||||
|
return new MultivaluedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Providers providers = ResteasyContext.getContextData(Providers.class);
|
||||||
|
MessageBodyReader<MultipartFormDataInput> multiPartProvider = providers.getMessageBodyReader(
|
||||||
|
MultipartFormDataInput.class, null, null, MULTIPART_FORM_DATA_TYPE);
|
||||||
|
MultipartFormDataInput inputs = multiPartProvider
|
||||||
|
.readFrom(null, null, null, mediaType, getHttpHeaders().getRequestHeaders(),
|
||||||
|
delegate.getInputStream());
|
||||||
|
MultivaluedHashMap<String, FormPartValue> parts = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<InputPart>> entry : inputs.getFormDataMap().entrySet()) {
|
||||||
|
for (InputPart value : entry.getValue()) {
|
||||||
|
MediaType valueMediaType = value.getMediaType();
|
||||||
|
|
||||||
|
if (TEXT_PLAIN_TYPE.isCompatible(valueMediaType)) {
|
||||||
|
parts.add(entry.getKey(), new FormPartValueImpl(value.getBodyAsString()));
|
||||||
|
} else {
|
||||||
|
parts.add(entry.getKey(), new FormPartValueImpl(value.getBody(InputStream.class, null)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
} catch (IOException cause) {
|
||||||
|
throw new RuntimeException("Failed to parse multi part request", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders getHttpHeaders() {
|
public HttpHeaders getHttpHeaders() {
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.keycloak.urls.UrlType;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.HttpMethod;
|
import javax.ws.rs.HttpMethod;
|
||||||
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
@ -223,6 +224,18 @@ public class AdminRoot {
|
||||||
return new RealmsAdminResource(session, auth, tokenManager);
|
return new RealmsAdminResource(session, auth, tokenManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("{any:.*}")
|
||||||
|
@OPTIONS
|
||||||
|
public Object preFlight() {
|
||||||
|
HttpRequest request = getHttpRequest();
|
||||||
|
|
||||||
|
if (!isAdminApiEnabled()) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdminCorsPreflightService(request);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General information about the server
|
* General information about the server
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
package org.keycloak.services.resources.admin;
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
|
||||||
import javax.ws.rs.NotAcceptableException;
|
import javax.ws.rs.NotAcceptableException;
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
|
||||||
|
@ -29,6 +27,7 @@ import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.http.FormPartValue;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKParser;
|
import org.keycloak.jose.jwk.JWKParser;
|
||||||
|
@ -52,6 +51,7 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -61,8 +61,6 @@ import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -140,11 +138,11 @@ public class ClientAttributeCertificateResource {
|
||||||
@Path("upload")
|
@Path("upload")
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public CertificateRepresentation uploadJks(MultipartFormDataInput input) throws IOException {
|
public CertificateRepresentation uploadJks() throws IOException {
|
||||||
auth.clients().requireConfigure(client);
|
auth.clients().requireConfigure(client);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CertificateRepresentation info = getCertFromRequest(input);
|
CertificateRepresentation info = getCertFromRequest();
|
||||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
||||||
|
@ -165,11 +163,11 @@ public class ClientAttributeCertificateResource {
|
||||||
@Path("upload-certificate")
|
@Path("upload-certificate")
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public CertificateRepresentation uploadJksCertificate(MultipartFormDataInput input) throws IOException {
|
public CertificateRepresentation uploadJksCertificate() throws IOException {
|
||||||
auth.clients().requireConfigure(client);
|
auth.clients().requireConfigure(client);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CertificateRepresentation info = getCertFromRequest(input);
|
CertificateRepresentation info = getCertFromRequest();
|
||||||
info.setPrivateKey(null);
|
info.setPrivateKey(null);
|
||||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||||
|
|
||||||
|
@ -180,16 +178,16 @@ public class ClientAttributeCertificateResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CertificateRepresentation getCertFromRequest(MultipartFormDataInput input) throws IOException {
|
private CertificateRepresentation getCertFromRequest() throws IOException {
|
||||||
auth.clients().requireManage(client);
|
auth.clients().requireManage(client);
|
||||||
CertificateRepresentation info = new CertificateRepresentation();
|
CertificateRepresentation info = new CertificateRepresentation();
|
||||||
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
|
MultivaluedMap<String, FormPartValue> uploadForm = session.getContext().getHttpRequest().getMultiPartFormParameters();
|
||||||
List<InputPart> keystoreFormatPart = uploadForm.get("keystoreFormat");
|
FormPartValue keystoreFormatPart = uploadForm.getFirst("keystoreFormat");
|
||||||
if (keystoreFormatPart == null) throw new BadRequestException();
|
if (keystoreFormatPart == null) throw new BadRequestException();
|
||||||
String keystoreFormat = keystoreFormatPart.get(0).getBodyAsString();
|
String keystoreFormat = keystoreFormatPart.asString();
|
||||||
List<InputPart> inputParts = uploadForm.get("file");
|
FormPartValue inputParts = uploadForm.getFirst("file");
|
||||||
if (keystoreFormat.equals(CERTIFICATE_PEM)) {
|
if (keystoreFormat.equals(CERTIFICATE_PEM)) {
|
||||||
String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null));
|
String pem = StreamUtil.readString(inputParts.asInputStream());
|
||||||
|
|
||||||
pem = PemUtils.removeBeginEnd(pem);
|
pem = PemUtils.removeBeginEnd(pem);
|
||||||
|
|
||||||
|
@ -199,7 +197,7 @@ public class ClientAttributeCertificateResource {
|
||||||
info.setCertificate(pem);
|
info.setCertificate(pem);
|
||||||
return info;
|
return info;
|
||||||
} else if (keystoreFormat.equals(PUBLIC_KEY_PEM)) {
|
} else if (keystoreFormat.equals(PUBLIC_KEY_PEM)) {
|
||||||
String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null));
|
String pem = StreamUtil.readString(inputParts.asInputStream());
|
||||||
|
|
||||||
// Validate format
|
// Validate format
|
||||||
KeycloakModelUtils.getPublicKey(pem);
|
KeycloakModelUtils.getPublicKey(pem);
|
||||||
|
@ -207,7 +205,7 @@ public class ClientAttributeCertificateResource {
|
||||||
info.setPublicKey(pem);
|
info.setPublicKey(pem);
|
||||||
return info;
|
return info;
|
||||||
} else if (keystoreFormat.equals(JSON_WEB_KEY_SET)) {
|
} else if (keystoreFormat.equals(JSON_WEB_KEY_SET)) {
|
||||||
InputStream stream = inputParts.get(0).getBody(InputStream.class, null);
|
InputStream stream = inputParts.asInputStream();
|
||||||
JSONWebKeySet keySet = JsonSerialization.readValue(stream, JSONWebKeySet.class);
|
JSONWebKeySet keySet = JsonSerialization.readValue(stream, JSONWebKeySet.class);
|
||||||
JWK publicKeyJwk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
JWK publicKeyJwk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
||||||
if (publicKeyJwk == null) {
|
if (publicKeyJwk == null) {
|
||||||
|
@ -222,17 +220,17 @@ public class ClientAttributeCertificateResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String keyAlias = uploadForm.get("keyAlias").get(0).getBodyAsString();
|
String keyAlias = uploadForm.getFirst("keyAlias").asString();
|
||||||
List<InputPart> keyPasswordPart = uploadForm.get("keyPassword");
|
FormPartValue keyPasswordPart = uploadForm.getFirst("keyPassword");
|
||||||
char[] keyPassword = keyPasswordPart != null ? keyPasswordPart.get(0).getBodyAsString().toCharArray() : null;
|
char[] keyPassword = keyPasswordPart != null ? keyPasswordPart.asString().toCharArray() : null;
|
||||||
|
|
||||||
List<InputPart> storePasswordPart = uploadForm.get("storePassword");
|
FormPartValue storePasswordPart = uploadForm.getFirst("storePassword");
|
||||||
char[] storePassword = storePasswordPart != null ? storePasswordPart.get(0).getBodyAsString().toCharArray() : null;
|
char[] storePassword = storePasswordPart != null ? storePasswordPart.asString().toCharArray() : null;
|
||||||
PrivateKey privateKey = null;
|
PrivateKey privateKey = null;
|
||||||
X509Certificate certificate = null;
|
X509Certificate certificate = null;
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(keystoreFormat));
|
KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(keystoreFormat));
|
||||||
keyStore.load(inputParts.get(0).getBody(InputStream.class, null), storePassword);
|
keyStore.load(inputParts.asInputStream(), storePassword);
|
||||||
try {
|
try {
|
||||||
privateKey = (PrivateKey)keyStore.getKey(keyAlias, keyPassword);
|
privateKey = (PrivateKey)keyStore.getKey(keyAlias, keyPassword);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.ManagementPermissionReference;
|
import org.keycloak.representations.idm.ManagementPermissionReference;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||||
|
@ -169,8 +170,12 @@ public class GroupResource {
|
||||||
child = realm.createGroup(groupName, group);
|
child = realm.createGroup(groupName, group);
|
||||||
updateGroup(rep, child, realm, session);
|
updateGroup(rep, child, realm, session);
|
||||||
URI uri = session.getContext().getUri().getBaseUriBuilder()
|
URI uri = session.getContext().getUri().getBaseUriBuilder()
|
||||||
.path(session.getContext().getUri().getMatchedURIs().get(2))
|
.path(AdminRoot.class)
|
||||||
.path(child.getId()).build();
|
.path(AdminRoot.class, "getRealmsAdmin")
|
||||||
|
.path(RealmsAdminResource.class, "getRealmAdmin")
|
||||||
|
.path(RealmAdminResource.class, "getGroups")
|
||||||
|
.path(GroupsResource.class, "getGroupById")
|
||||||
|
.build(realm.getName(), child.getId());
|
||||||
builder.status(201).location(uri);
|
builder.status(201).location(uri);
|
||||||
rep.setId(child.getId());
|
rep.setId(child.getId());
|
||||||
adminEvent.operation(OperationType.CREATE);
|
adminEvent.operation(OperationType.CREATE);
|
||||||
|
|
|
@ -19,14 +19,13 @@ package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
import org.keycloak.connections.httpclient.HttpClientProvider;
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.http.FormPartValue;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
@ -47,10 +46,10 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -106,15 +105,14 @@ public class IdentityProvidersResource {
|
||||||
@Path("import-config")
|
@Path("import-config")
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Map<String, String> importFrom(MultipartFormDataInput input) throws IOException {
|
public Map<String, String> importFrom() throws IOException {
|
||||||
this.auth.realm().requireManageIdentityProviders();
|
this.auth.realm().requireManageIdentityProviders();
|
||||||
Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
|
MultivaluedMap<String, FormPartValue> formDataMap = session.getContext().getHttpRequest().getMultiPartFormParameters();
|
||||||
if (!(formDataMap.containsKey("providerId") && formDataMap.containsKey("file"))) {
|
if (!(formDataMap.containsKey("providerId") && formDataMap.containsKey("file"))) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
|
String providerId = formDataMap.getFirst("providerId").asString();
|
||||||
InputPart file = formDataMap.get("file").get(0);
|
InputStream inputStream = formDataMap.getFirst("file").asInputStream();
|
||||||
InputStream inputStream = file.getBody(InputStream.class, null);
|
|
||||||
IdentityProviderFactory providerFactory = getProviderFactoryById(providerId);
|
IdentityProviderFactory providerFactory = getProviderFactoryById(providerId);
|
||||||
Map<String, String> config = providerFactory.parseConfig(session, inputStream);
|
Map<String, String> config = providerFactory.parseConfig(session, inputStream);
|
||||||
return config;
|
return config;
|
||||||
|
@ -133,7 +131,7 @@ public class IdentityProvidersResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Map<String, String> importFrom(Map<String, Object> data) throws IOException {
|
public Map<String, String> importFrom(Map<String, Object> data) throws IOException {
|
||||||
this.auth.realm().requireManageIdentityProviders();
|
this.auth.realm().requireManageIdentityProviders();
|
||||||
if (!(data.containsKey("providerId") && data.containsKey("fromUrl"))) {
|
if (data == null || !(data.containsKey("providerId") && data.containsKey("fromUrl"))) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -540,6 +540,7 @@ public class RealmAdminResource {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Path("push-revocation")
|
@Path("push-revocation")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@POST
|
@POST
|
||||||
public GlobalRequestResult pushRevocation() {
|
public GlobalRequestResult pushRevocation() {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
@ -1010,6 +1011,7 @@ public class RealmAdminResource {
|
||||||
*/
|
*/
|
||||||
@Path("partialImport")
|
@Path("partialImport")
|
||||||
@POST
|
@POST
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public Response partialImport(InputStream requestBody) {
|
public Response partialImport(InputStream requestBody) {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
|
@ -1065,6 +1067,7 @@ public class RealmAdminResource {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Path("partial-export")
|
@Path("partial-export")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@POST
|
@POST
|
||||||
public Response partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
|
public Response partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
|
||||||
@QueryParam("exportClients") Boolean exportClients) {
|
@QueryParam("exportClients") Boolean exportClients) {
|
||||||
|
|
|
@ -19,8 +19,7 @@ package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
|
import org.keycloak.http.FormPartValue;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -28,7 +27,6 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluato
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -45,6 +43,7 @@ import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.utils.StringUtil;
|
import org.keycloak.utils.StringUtil;
|
||||||
|
|
||||||
|
@ -82,16 +81,14 @@ public class RealmLocalizationResource {
|
||||||
@POST
|
@POST
|
||||||
@Path("{locale}")
|
@Path("{locale}")
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
public void createOrUpdateRealmLocalizationTextsFromFile(@PathParam("locale") String locale,
|
public void createOrUpdateRealmLocalizationTextsFromFile(@PathParam("locale") String locale) {
|
||||||
MultipartFormDataInput input) {
|
|
||||||
this.auth.realm().requireManageRealm();
|
this.auth.realm().requireManageRealm();
|
||||||
|
|
||||||
Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
|
MultivaluedMap<String, FormPartValue> formDataMap = session.getContext().getHttpRequest().getMultiPartFormParameters();
|
||||||
if (!formDataMap.containsKey("file")) {
|
if (!formDataMap.containsKey("file")) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
InputPart file = formDataMap.get("file").get(0);
|
try (InputStream inputStream = formDataMap.getFirst("file").asInputStream()) {
|
||||||
try (InputStream inputStream = file.getBody(InputStream.class, null)) {
|
|
||||||
TypeReference<HashMap<String, String>> typeRef = new TypeReference<HashMap<String, String>>() {
|
TypeReference<HashMap<String, String>> typeRef = new TypeReference<HashMap<String, String>>() {
|
||||||
};
|
};
|
||||||
Map<String, String> rep = JsonSerialization.readValue(inputStream, typeRef);
|
Map<String, String> rep = JsonSerialization.readValue(inputStream, typeRef);
|
||||||
|
|
0
services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java
Executable file → Normal file
0
services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java
Executable file → Normal file
|
@ -1893,7 +1893,7 @@ public class OAuthClient {
|
||||||
|
|
||||||
Header[] contentTypeHeaders = response.getHeaders("Content-Type");
|
Header[] contentTypeHeaders = response.getHeaders("Content-Type");
|
||||||
String contentType = (contentTypeHeaders != null && contentTypeHeaders.length > 0) ? contentTypeHeaders[0].getValue() : null;
|
String contentType = (contentTypeHeaders != null && contentTypeHeaders.length > 0) ? contentTypeHeaders[0].getValue() : null;
|
||||||
if (!"application/json".equals(contentType)) {
|
if (contentType == null || !contentType.startsWith("application/json")) {
|
||||||
Assert.fail("Invalid content type. Status: " + statusCode + ", contentType: " + contentType);
|
Assert.fail("Invalid content type. Status: " + statusCode + ", contentType: " + contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,8 @@
|
||||||
package org.keycloak.testsuite.admin;
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
|
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
@ -642,12 +640,12 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
public void invoke(RealmResource realm) {
|
public void invoke(RealmResource realm) {
|
||||||
realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJks(new MultipartFormDataOutput());
|
realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJks(null);
|
||||||
}
|
}
|
||||||
}, Resource.CLIENT, true);
|
}, Resource.CLIENT, true);
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
public void invoke(RealmResource realm) {
|
public void invoke(RealmResource realm) {
|
||||||
realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJksCertificate(new MultipartFormDataOutput());
|
realm.clients().get(foo.getId()).getCertficateResource("nosuch").uploadJksCertificate(null);
|
||||||
}
|
}
|
||||||
}, Resource.CLIENT, true);
|
}, Resource.CLIENT, true);
|
||||||
|
|
||||||
|
@ -1727,7 +1725,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
}, Resource.IDENTITY_PROVIDER, true);
|
}, Resource.IDENTITY_PROVIDER, true);
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
public void invoke(RealmResource realm) {
|
public void invoke(RealmResource realm) {
|
||||||
realm.identityProviders().importFrom(new MultipartFormDataOutput());
|
realm.identityProviders().importFrom(null);
|
||||||
}
|
}
|
||||||
}, Resource.IDENTITY_PROVIDER, true);
|
}, Resource.IDENTITY_PROVIDER, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,7 @@ public class URLAssert {
|
||||||
public void assertResponse(CloseableHttpResponse response) throws IOException {
|
public void assertResponse(CloseableHttpResponse response) throws IOException {
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
Header contentType = entity.getContentType();
|
Header contentType = entity.getContentType();
|
||||||
Assert.assertEquals("application/json", contentType.getValue());
|
Assert.assertTrue(contentType.getValue().startsWith("application/json"));
|
||||||
|
|
||||||
char [] buf = new char[8192];
|
char [] buf = new char[8192];
|
||||||
StringWriter out = new StringWriter();
|
StringWriter out = new StringWriter();
|
||||||
|
|
Loading…
Reference in a new issue