Skip to content

Commit

Permalink
[JENKINS-61747] Do not write the SP metadata with every login (#98)
Browse files Browse the repository at this point in the history
* test: add test for the cache class

* chore: dual implumentations

* feat: make the selection configurable

* fix: reimplement tests

* Delete settings.json

* fix: NPE when advanced config is not set
  • Loading branch information
kuisathaverat authored Oct 23, 2022
1 parent 3902210 commit 6cc620b
Show file tree
Hide file tree
Showing 8 changed files with 553 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
Expand All @@ -37,6 +38,8 @@ public class SamlAdvancedConfiguration extends AbstractDescribableImpl<SamlAdvan
private final String spEntityId;
private final String nameIdPolicyFormat;

private Boolean useDiskCache = false;

@DataBoundConstructor
public SamlAdvancedConfiguration(Boolean forceAuthn,
String authnContextClassRef,
Expand Down Expand Up @@ -64,12 +67,22 @@ public String getNameIdPolicyFormat() {
return nameIdPolicyFormat;
}

public Boolean getUseDiskCache() {
return useDiskCache;
}

@DataBoundSetter
public void setUseDiskCache(Boolean useDiskCache) {
this.useDiskCache = useDiskCache;
}

@Override
public String toString() {
return "SamlAdvancedConfiguration{" + "forceAuthn=" + getForceAuthn() + ", authnContextClassRef='"
+ StringUtils.defaultIfBlank(getAuthnContextClassRef(), "none") + '\'' + ", spEntityId='"
+ StringUtils.defaultIfBlank(getSpEntityId(), "none") + '\'' + ", nameIdPolicyFormat='"
+ StringUtils.defaultIfBlank(getNameIdPolicyFormat(), "none") + '\'' + '}';
+ StringUtils.defaultIfBlank(getNameIdPolicyFormat(), "none") + '\''
+ "useDiskCache=" + getUseDiskCache() + '}';
}

@SuppressWarnings("unused")
Expand Down
62 changes: 36 additions & 26 deletions src/main/java/org/jenkinsci/plugins/saml/SamlFileResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;
import org.pac4j.core.exception.TechnicalException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;

Expand All @@ -38,31 +34,45 @@
*/
class SamlFileResource implements WritableResource {

private final String fileName;
private final WritableResource resource;

public SamlFileResource(@Nonnull String fileName) {
this.fileName = fileName;
if(getUseDiskCache()){
this.resource = new SamlFileResourceCache(fileName);
} else {
this.resource = new SamlFileResourceDisk(fileName);
}
}

public SamlFileResource(@Nonnull String fileName, @Nonnull String data) {
this.fileName = fileName;
try {
FileUtils.writeByteArrayToFile(getFile(), data.getBytes(StandardCharsets.UTF_8));
} catch (UnsupportedEncodingException e) {
throw new TechnicalException("Could not get string bytes.", e);
} catch (java.io.IOException e) {
throw new TechnicalException("Could not save the " + fileName + " file.", e);
if(getUseDiskCache()){
this.resource = new SamlFileResourceCache(fileName, data);
} else {
this.resource = new SamlFileResourceDisk(fileName, data);
}
}

private boolean getUseDiskCache() {
boolean ret = false;
jenkins.model.Jenkins j = jenkins.model.Jenkins.get();
if (j.getSecurityRealm() instanceof SamlSecurityRealm) {
SamlSecurityRealm samlSecurityRealm = (SamlSecurityRealm) j.getSecurityRealm();
SamlAdvancedConfiguration config = samlSecurityRealm.getAdvancedConfiguration();
if(config != null ) {
ret = config.getUseDiskCache();
}
}
return ret;
}

@Override
public boolean exists() {
return getFile().exists();
return resource.exists();
}

@Override
public boolean isReadable() {
return getFile().canRead();
return resource.isReadable();
}

@Override
Expand All @@ -84,35 +94,35 @@ public URI getURI() {

@Override
public String getFilename() {
return fileName;
return resource.getFilename();
}

@NonNull
@Override
public String getDescription() {
return fileName;
return resource.getDescription();
}

@NonNull
@Override
public InputStream getInputStream() throws IOException {
return FileUtils.openInputStream(getFile());
return resource.getInputStream();
}

@NonNull
@Override
public File getFile() {
return new File(fileName);
public File getFile() throws IOException {
return resource.getFile();
}

@Override
public long contentLength() {
return getFile().length();
public long contentLength() throws IOException {
return resource.contentLength();
}

@Override
public long lastModified() {
return getFile().lastModified();
public long lastModified() throws IOException {
return resource.lastModified();
}

@NonNull
Expand All @@ -123,12 +133,12 @@ public Resource createRelative(@NonNull String s) {

@Override
public boolean isWritable() {
return getFile().canWrite();
return resource.isWritable();
}

@NonNull
@Override
public OutputStream getOutputStream() throws IOException {
return FileUtils.openOutputStream(getFile());
return resource.getOutputStream();
}
}
159 changes: 159 additions & 0 deletions src/main/java/org/jenkinsci/plugins/saml/SamlFileResourceCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* Licensed to Jenkins CI under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jenkins CI licenses this file to you 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.jenkinsci.plugins.saml;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.pac4j.core.exception.TechnicalException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;

import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
* Class to manage the metadata files using cache.
* It will only write the files if the content is different.
*/
class SamlFileResourceCache implements WritableResource {

private static final Logger LOG = Logger.getLogger(SamlFileResource.class.getName());

private String fileName;

private final static Map<String,String> cache = new HashMap<>();

public SamlFileResourceCache(@Nonnull String fileName) {
this.fileName = fileName;
}

public SamlFileResourceCache(@Nonnull String fileName, @Nonnull String data) {
this.fileName = fileName;
try {
save(fileName, data);
} catch (UnsupportedEncodingException e) {
throw new TechnicalException("Could not get string bytes.", e);
} catch (java.io.IOException e) {
throw new TechnicalException("Could not save the " + fileName + " file.", e);
}
}

@Override
public boolean exists() {
return new File(fileName).exists();
}

@Override
public boolean isReadable() {
return new File(fileName).canRead();
}

@Override
public boolean isOpen() {
return false;
}

@Override
public URL getURL() {
throw new NotImplementedException();
}

@Override
public URI getURI() {
throw new NotImplementedException();
}

@Override
public String getFilename() {
return fileName;
}

@Override
public String getDescription() {
return fileName;
}

@Override
public InputStream getInputStream() throws IOException {
if (cache.containsKey(fileName)){
return IOUtils.toInputStream(cache.get(fileName),"UTF-8");
} else {
return FileUtils.openInputStream(new File(fileName));
}
}

@Override
public File getFile() {
throw new NotImplementedException();
}

@Override
public long contentLength() {
return new File(fileName).length();
}

@Override
public long lastModified() {
return new File(fileName).lastModified();
}

@Override
public Resource createRelative(String s) {
throw new NotImplementedException();
}

@Override
public boolean isWritable() {
return new File(fileName).canWrite();
}

@Override
public OutputStream getOutputStream() throws IOException {
return new ByteArrayOutputStream(){
@Override
public void close() throws IOException {
save(fileName, IOUtils.toString(this.buf, "UTF-8").trim());
}
};
}

private boolean isNew(String fileName, String data){
String oldData = cache.containsKey(fileName) ? cache.get(fileName) : "";
String md5SumNew = org.apache.commons.codec.digest.DigestUtils.md5Hex(data);
String md5SumOld = org.apache.commons.codec.digest.DigestUtils.md5Hex(oldData);
return !md5SumNew.equals(md5SumOld);
}

private void save(@Nonnull String fileName, @Nonnull String data) throws IOException {
if(isNew(fileName, data)) {
FileUtils.writeByteArrayToFile(new File(fileName), data.getBytes("UTF-8"));
cache.put(fileName, data);
}
}
}
Loading

0 comments on commit 6cc620b

Please sign in to comment.