Skip to content

Commit

Permalink
Added API to generate and store tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
mackdk committed Dec 13, 2024
1 parent e53eb1c commit 2142bbc
Show file tree
Hide file tree
Showing 11 changed files with 656 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/*
* Copyright (c) 2024 SUSE LLC
* Copyright (c) 2009--2010 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
Expand Down Expand Up @@ -95,6 +96,7 @@
import com.suse.manager.webui.controllers.bootstrap.SSHMinionBootstrapper;
import com.suse.manager.webui.services.iface.SaltApi;
import com.suse.manager.xmlrpc.admin.AdminPaygHandler;
import com.suse.manager.xmlrpc.iss.SyncHandler;
import com.suse.manager.xmlrpc.maintenance.MaintenanceHandler;

import java.util.HashMap;
Expand Down Expand Up @@ -209,6 +211,7 @@ public static HandlerFactory getDefaultHandlerFactory() {
factory.addHandler("saltkey", new SaltKeyHandler(saltKeyUtils));
factory.addHandler("schedule", new ScheduleHandler());
factory.addHandler("subscriptionmatching.pinnedsubscription", new PinnedSubscriptionHandler());
factory.addHandler("sync.iss", new SyncHandler());
factory.addHandler("sync.master", new MasterHandler());
factory.addHandler("sync.slave", new SlaveHandler());
factory.addHandler("sync.content", new ContentSyncHandler());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*/
package com.redhat.rhn.frontend.xmlrpc;

import com.redhat.rhn.FaultException;

/**
* Token creation failed.
*
*/
public class InvalidTokenException extends FaultException {

/**
* Constructor
*/
public InvalidTokenException() {
super(11000 , "invalidToken" , "Invalid token");
}

/**
* Constructor
*
* @param message exception message
*/
public InvalidTokenException(String message) {
super(11000 , "invalidToken" , message);
}

/**
* Constructor
* @param cause the cause (which is saved for later retrieval
* by the Throwable.getCause() method). (A null value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public InvalidTokenException(Throwable cause) {
super(11000 , "invalidToken" , "Invalid token", cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
/*
* Copyright (c) 2016 SUSE LLC
* Copyright (c) 2016--2024 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.frontend.xmlrpc.activationkey;
package com.redhat.rhn.frontend.xmlrpc;

import com.redhat.rhn.FaultException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.redhat.rhn.frontend.xmlrpc.InvalidChannelException;
import com.redhat.rhn.frontend.xmlrpc.InvalidServerGroupException;
import com.redhat.rhn.frontend.xmlrpc.NoSuchSystemException;
import com.redhat.rhn.frontend.xmlrpc.TokenCreationException;
import com.redhat.rhn.frontend.xmlrpc.ValidationException;
import com.redhat.rhn.frontend.xmlrpc.configchannel.XmlRpcConfigChannelHelper;
import com.redhat.rhn.manager.channel.ChannelManager;
Expand Down
39 changes: 39 additions & 0 deletions java/code/src/com/suse/manager/model/hub/HubFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

Expand Down Expand Up @@ -85,4 +86,42 @@ public List<IssPeripheralChannels> listIssPeripheralChannelsByChannels(Channel c
.setParameter("channel", channelIn)
.list();
}

/**
* Store a new access token
* @param fqdn the FQDN of the server
* @param token the token to establish a connection with the specified server
* @param type the type of token
* @param expiration when the token is no longer valid
*/
public void saveToken(String fqdn, String token, TokenType type, Instant expiration) {
var accessToken = new IssAccessToken(type, token, fqdn, expiration);
getSession().save(accessToken);
}

/**
* Returns the issued access token information matching the given token
* @param token the string representation of the token
* @return the issued token, if present
*/
public IssAccessToken lookupIssuedToken(String token) {
return getSession()
.createQuery("FROM IssAccessToken k WHERE k.type = :type AND k.token = :token", IssAccessToken.class)
.setParameter("type", TokenType.ISSUED)
.setParameter("token", token)
.uniqueResult();
}

/**
* Returns the access token for the specified FQDN
* @param fqdn the FQDN of the peripheral/hub
* @return the access token associated to the entity, if present
*/
public IssAccessToken lookupAccessTokenFor(String fqdn) {
return getSession()
.createQuery("FROM IssAccessToken k WHERE k.type = :type AND k.serverFqdn = :fqdn", IssAccessToken.class)
.setParameter("type", TokenType.CONSUMED)
.setParameter("fqdn", fqdn)
.uniqueResult();
}
}
86 changes: 86 additions & 0 deletions java/code/src/com/suse/manager/model/hub/HubManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2024 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*/

package com.suse.manager.model.hub;

import com.redhat.rhn.common.conf.ConfigDefaults;

import com.suse.manager.webui.utils.token.IssTokenBuilder;
import com.suse.manager.webui.utils.token.Token;
import com.suse.manager.webui.utils.token.TokenBuildingException;
import com.suse.manager.webui.utils.token.TokenException;
import com.suse.manager.webui.utils.token.TokenParser;
import com.suse.manager.webui.utils.token.TokenParsingException;

/**
* Business logic to manage ISSv3 Sync
*/
public class HubManager {

private final HubFactory hubFactory;

/**
* Default constructor
*/
public HubManager() {
this(new HubFactory());
}

/**
* Builds an instance with the given dependencies
* @param hubFactoryIn the hub factory
*/
public HubManager(HubFactory hubFactoryIn) {
this.hubFactory = hubFactoryIn;
}

/**
* Create a new access token for the given FQDN and store it in the database
* @param fqdn the FQDN of the peripheral/hub
* @return the serialized form of the token
* @throws TokenBuildingException when an error occurs during generation
* @throws TokenParsingException when the generated token cannot be parsed
*/
public String issueAccessToken(String fqdn) throws TokenException {
Token token = new IssTokenBuilder(fqdn)
.usingServerSecret()
.build();

hubFactory.saveToken(fqdn, token.getSerializedForm(), TokenType.ISSUED, token.getExpirationTime());
return token.getSerializedForm();
}

/**
* Stores in the database the access token of the given FQDN
* @param fqdn the FQDN of the peripheral/hub that generated this token
* @param token the token
* @throws TokenParsingException when it's not possible to process the token
*/
public void storeAccessToken(String fqdn, String token) throws TokenParsingException {
// We do not need to verify the signature as this token is for accessing another system.
// That system will take care of ensuring its authenticity
Token parsedToken = new TokenParser()
.skippingSignatureVerification()
.verifyingExpiration()
.verifyingNotBefore()
.parse(token);

// Verify if this token is for this system
String targetFqdn = parsedToken.getClaim("fqdn", String.class);
String hostname = ConfigDefaults.get().getHostname();

if (targetFqdn == null || !targetFqdn.equals(hostname)) {
throw new TokenParsingException("FQDN do not match. Expected %s got %s".formatted(hostname, targetFqdn));
}

hubFactory.saveToken(fqdn, token, TokenType.CONSUMED, parsedToken.getExpirationTime());
}
}
34 changes: 34 additions & 0 deletions java/code/src/com/suse/manager/model/hub/test/HubFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.redhat.rhn.common.hibernate.HibernateFactory;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.channel.ChannelFactory;
import com.redhat.rhn.domain.channel.test.ChannelFactoryTest;
Expand All @@ -25,13 +26,18 @@
import com.redhat.rhn.testing.TestUtils;

import com.suse.manager.model.hub.HubFactory;
import com.suse.manager.model.hub.IssAccessToken;
import com.suse.manager.model.hub.IssHub;
import com.suse.manager.model.hub.IssPeripheral;
import com.suse.manager.model.hub.IssPeripheralChannels;
import com.suse.manager.model.hub.TokenType;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -151,4 +157,32 @@ public void testCreateIssPeripheralChannels() throws Exception {
List<IssPeripheralChannels> pcWithChild = hubFactory.listIssPeripheralChannelsByChannels(childChannel);
assertEquals(1, pcWithChild.size());
}

@Test
public void testCreateAndLookupTokens() {
Instant expiration = Instant.now().truncatedTo(ChronoUnit.MINUTES).plus(60, ChronoUnit.DAYS);

long currentTokens = HibernateFactory.getSession()
.createQuery("SELECT COUNT(*) FROM IssAccessToken at", Long.class)
.uniqueResult();

hubFactory.saveToken("uyuni-hub.dev.local", "dummy-hub-token", TokenType.ISSUED, expiration);
hubFactory.saveToken("uyuni-peripheral.dev.local", "dummy-peripheral-token", TokenType.CONSUMED, expiration);

assertEquals(currentTokens + 2, HibernateFactory.getSession()
.createQuery("SELECT COUNT(*) FROM IssAccessToken at", Long.class)
.uniqueResult());

IssAccessToken hubAccessToken = hubFactory.lookupIssuedToken("dummy-hub-token");
assertNotNull(hubAccessToken);
assertEquals("uyuni-hub.dev.local", hubAccessToken.getServerFqdn());
assertEquals(TokenType.ISSUED, hubAccessToken.getType());
assertEquals(Date.from(expiration), hubAccessToken.getExpirationDate());

IssAccessToken peripheralAccessToken = hubFactory.lookupAccessTokenFor("uyuni-peripheral.dev.local");
assertNotNull(peripheralAccessToken);
assertEquals("dummy-peripheral-token", peripheralAccessToken.getToken());
assertEquals(TokenType.CONSUMED, peripheralAccessToken.getType());
assertEquals(Date.from(expiration), peripheralAccessToken.getExpirationDate());
}
}
Loading

0 comments on commit 2142bbc

Please sign in to comment.