From 00edd24274ae7cb975d27fb4cafc7d109158a17b Mon Sep 17 00:00:00 2001 From: "Crystal.Sea" Date: Wed, 9 Sep 2020 07:08:19 +0800 Subject: [PATCH] CAS REST --- .../maxkey-protocol-cas/build.gradle | 6 + .../authz/cas/endpoint/CasRestV1Endpoint.java | 184 ++++++++++++++++ .../cas/endpoint/ticket/CasConstants.java | 6 + .../cas/endpoint/ticket/ExpirationPolicy.java | 58 +++++ .../cas/endpoint/ticket/ServiceTicket.java | 63 ++++++ .../endpoint/ticket/ServiceTicketImpl.java | 22 +- .../endpoint/ticket/TicketGrantingTicket.java | 115 ++++++++++ .../ticket/TicketGrantingTicketImpl.java | 198 ++++++++++++++++++ .../cas/endpoint/ticket/TicketState.java | 66 ++++++ .../ticket/proxy/ProxyGrantingTicket.java | 41 ++++ .../endpoint/ticket/proxy/ProxyTicket.java | 18 ++ .../service/RandomServiceTicketServices.java | 6 +- .../ticket/service/RedisTicketServices.java | 2 +- .../web/authorize/endpoint/CasRestClient.java | 132 ++++++++++++ .../authorize/endpoint/RestTestClient.java | 51 +++++ .../main/java/org/maxkey/MaxKeyMvcConfig.java | 3 + 16 files changed, 966 insertions(+), 5 deletions(-) create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/CasRestV1Endpoint.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ExpirationPolicy.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicket.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicket.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicketImpl.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketState.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyGrantingTicket.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyTicket.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/CasRestClient.java create mode 100644 maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/RestTestClient.java diff --git a/maxkey-protocols/maxkey-protocol-cas/build.gradle b/maxkey-protocols/maxkey-protocol-cas/build.gradle index b5c921424..a8f48bf0e 100644 --- a/maxkey-protocols/maxkey-protocol-cas/build.gradle +++ b/maxkey-protocols/maxkey-protocol-cas/build.gradle @@ -6,6 +6,12 @@ dependencies { //local jars compile fileTree(dir: '../maxkey-lib/*/', include: '*.jar') + + testCompile group: 'org.pac4j', name: 'pac4j-core', version: '3.1.0' + // https://mvnrepository.com/artifact/org.pac4j/pac4j-cas + testCompile group: 'org.pac4j', name: 'pac4j-cas', version: '3.1.0' + + compile project(":maxkey-core") compile project(":maxkey-persistence") compile project(":maxkey-protocols:maxkey-protocol-authorize") diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/CasRestV1Endpoint.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/CasRestV1Endpoint.java new file mode 100644 index 000000000..33c38a051 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/CasRestV1Endpoint.java @@ -0,0 +1,184 @@ +/* + * Copyright [2020] [MaxKey of copyright http://www.maxkey.top] + * + * 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.maxkey.authz.cas.endpoint; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.maxkey.authn.BasicAuthentication; +import org.maxkey.authn.realm.AbstractAuthenticationRealm; +import org.maxkey.authz.cas.endpoint.ticket.CasConstants; +import org.maxkey.authz.cas.endpoint.ticket.ServiceTicketImpl; +import org.maxkey.authz.cas.endpoint.ticket.TicketGrantingTicketImpl; +import org.maxkey.domain.UserInfo; +import org.maxkey.domain.apps.AppsCasDetails; +import org.maxkey.persistence.db.PasswordPolicyValidator; +import org.maxkey.web.WebContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * @author Crystal.Sea + * https://apereo.github.io/cas/6.2.x/protocol/REST-Protocol.html + */ +@Controller +public class CasRestV1Endpoint extends CasBaseAuthorizeEndpoint{ + final static Logger _logger = LoggerFactory.getLogger(CasRestV1Endpoint.class); + + @Autowired + protected PasswordPolicyValidator passwordPolicyValidator; + + @Autowired + @Qualifier("authenticationRealm") + protected AbstractAuthenticationRealm authenticationRealm; + + + @RequestMapping(value="/authz/cas/v1/tickets", + method=RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity casLoginRestTickets( + HttpServletRequest request, + HttpServletResponse response, + + @RequestParam(value=CasConstants.PARAMETER.SERVICE,required=false) String casService, + @RequestParam(value=CasConstants.PARAMETER.REST_USERNAME,required=true) String username, + @RequestParam(value=CasConstants.PARAMETER.REST_PASSWORD,required=true) String password){ + try { + if (password == null || password.isEmpty()) { + throw new BadCredentialsException("No credentials are provided or extracted to authenticate the REST request"); + } + + AbstractAuthenticationRealm authenticationRealm = + (AbstractAuthenticationRealm) WebContext.getBean("authenticationRealm"); + UserInfo loadeduserInfo = authenticationRealm.loadUserInfo(username, ""); + if (loadeduserInfo != null) { + + authenticationRealm.passwordMatches(loadeduserInfo, password); + + passwordPolicyValidator.passwordPolicyValid(loadeduserInfo); + + WebContext.setUserInfo(loadeduserInfo); + BasicAuthentication authentication =new BasicAuthentication(); + authentication.setUsername(username); + authentication.setPassword(password); + authentication.setAuthType("basic"); + + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken( + authentication, + "PASSWORD", + authenticationRealm.grantAuthority(loadeduserInfo) + ); + + authentication.setAuthenticated(true); + WebContext.setAuthentication(usernamePasswordAuthenticationToken); + WebContext.setUserInfo(loadeduserInfo); + + authenticationRealm.insertLoginHistory(loadeduserInfo, "CAS", "", "", "SUCCESS"); + + TicketGrantingTicketImpl ticketGrantingTicket=new TicketGrantingTicketImpl("Random",WebContext.getAuthentication(),null); + + String ticket=ticketServices.createTicket(ticketGrantingTicket); + String location = applicationConfig.getServerPrefix()+"/authz/cas/v1/tickets/" + ticket; + HttpHeaders headers = new HttpHeaders(); + headers.add("location", location); + return new ResponseEntity<>("Location: " + location, headers ,HttpStatus.CREATED); + + }else { + String message = WebContext.getI18nValue("login.error.username"); + _logger.debug("login user " + username + " not in this System ." + message); + throw new BadCredentialsException(WebContext.getI18nValue("login.error.username")); + } + } catch (final AuthenticationException e) { + _logger.error("BadCredentialsException ", e); + return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } catch (final Exception e) { + + _logger.error("Exception ", e); + return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @RequestMapping(value="/authz/cas/v1/tickets/{ticketGrantingTicket}", + method=RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity requestServiceTicket( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable("ticketGrantingTicket") String ticketGrantingTicket, + @RequestParam(value=CasConstants.PARAMETER.SERVICE,required=false) String casService, + @RequestParam(value=CasConstants.PARAMETER.RENEW,required=false) String renew, + @RequestParam(value=CasConstants.PARAMETER.REST_USERNAME,required=false) String username, + @RequestParam(value=CasConstants.PARAMETER.REST_PASSWORD,required=false) String password){ + try { + TicketGrantingTicketImpl ticketGrantingTicketImpl = + (TicketGrantingTicketImpl) ticketServices.consumeTicket(ticketGrantingTicket); + AppsCasDetails casDetails=new AppsCasDetails(); + if(casService.startsWith("http")) { + casDetails.setService(casService); + + List casDetailsList=casDetailsService.query(casDetails); + + casDetails=(casDetailsList!=null && casDetailsList.size()==1)?casDetailsList.get(0):null; + }else { + casDetails=casDetailsService.getAppDetails(casService); + } + + ServiceTicketImpl serviceTicket=new ServiceTicketImpl(ticketGrantingTicketImpl.getAuthentication(),casDetails); + String ticket=ticketServices.createTicket(serviceTicket); + return new ResponseEntity<>(ticket, HttpStatus.OK); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return new ResponseEntity<>("", HttpStatus.BAD_REQUEST); + } + @RequestMapping(value="/authz/cas/v1/users", + method=RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity casLoginRestUsers( + HttpServletRequest request, + HttpServletResponse response, + @RequestParam(value=CasConstants.PARAMETER.SERVICE,required=false) String casService, + @RequestParam(value=CasConstants.PARAMETER.REST_USERNAME,required=true) String username, + @RequestParam(value=CasConstants.PARAMETER.REST_PASSWORD,required=true) String password){ + + return null; + } + +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/CasConstants.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/CasConstants.java index 615640b96..cd4175e5f 100644 --- a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/CasConstants.java +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/CasConstants.java @@ -57,6 +57,10 @@ public class CasConstants { /** Constant representing the pgtIou parameter in the request. */ public static final String PROXY_GRANTING_TICKET_IOU = "pgtIou"; + + public static final String REST_USERNAME = "username"; + + public static final String REST_PASSWORD = "password"; } public static final class FORMAT_TYPE { @@ -83,6 +87,8 @@ public class CasConstants { public static final String PROXY_GRANTING_TICKET_PREFIX = "PGT"; /** The prefix to use when generating an id for a Proxy Granting Ticket IOU. */ public static final String PROXY_GRANTING_TICKET_IOU_PREFIX = "PGTIOU"; + + public static final String TICKET_GRANTING_TICKET_PREFIX = "TGT"; } /* CAS Protocol Error Codes. **/ diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ExpirationPolicy.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ExpirationPolicy.java new file mode 100644 index 000000000..e70004dc8 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ExpirationPolicy.java @@ -0,0 +1,58 @@ +package org.maxkey.authz.cas.endpoint.ticket; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.io.Serializable; + +/** + * Strategy that determines if the ticket is expired. Implementations of the + * Expiration Policy define their own rules on what they consider an expired + * Ticket to be. + * + * @author Scott Battaglia + * @see Ticket + * @since 3.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public interface ExpirationPolicy extends Serializable { + + /** + * Method to determine if a Ticket has expired or not, based on the policy. + * + * @param ticketState The snapshot of the current ticket state + * @return true if the ticket is expired, false otherwise. + */ + boolean isExpired(TicketState ticketState); + + /** + * Method to determine the actual TTL of a ticket, based on the policy. + * + * @param ticketState The snapshot of the current ticket state + * @return The time to live in seconds. A zero value indicates the time duration is not supported or is inactive. + */ + default Long getTimeToLive(final TicketState ticketState) { + return getTimeToLive(); + } + + /** + * Describes the time duration where this policy should consider the item alive. + * Once this time passes, the item is considered expired and dead. + * + * @return time to live in seconds. A zero value indicates the time duration is not supported or is inactive. + */ + Long getTimeToLive(); + + /** + * Describes the idle time duration for the item. + * + * @return idle time in seconds. A zero value indicates the time duration is not supported or is inactive. Unit of measure is defined by the implementation. + */ + Long getTimeToIdle(); + + /** + * Gets name of this expiration policy. + * + * @return the name + */ + String getName(); +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicket.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicket.java new file mode 100644 index 000000000..814acc570 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicket.java @@ -0,0 +1,63 @@ +package org.maxkey.authz.cas.endpoint.ticket; + +import org.maxkey.authz.cas.endpoint.ticket.proxy.ProxyGrantingTicket; +import org.springframework.security.core.Authentication; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Interface for a Service Ticket. A service ticket is used to grant access to a + * specific service for a principal. A Service Ticket is generally a one-time + * use ticket. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include= JsonTypeInfo.As.PROPERTY) +public interface ServiceTicket extends Ticket { + + /** + * Prefix generally applied to unique ids generated + * by UniqueTicketIdGenerator. + */ + String PREFIX = "ST"; + + /** + * Retrieve the service this ticket was given for. + * + * @return the server. + */ + Service getService(); + + /** + * Determine if this ticket was created at the same time as a + * TicketGrantingTicket. + * + * @return true if it is, false otherwise. + */ + boolean isFromNewLogin(); + + /** + * Attempts to ensure that the service specified matches the service associated with the ticket. + * + * @param service The incoming service to match this service ticket against. + * @return true, if the match is successful. + */ + boolean isValidFor(Service service); + + /** + * Method to grant a TicketGrantingTicket from this service to the + * authentication. Analogous to the ProxyGrantingTicket. + * + * @param id The unique identifier for this ticket. + * @param authentication The Authentication we wish to grant a ticket for. + * @param expirationPolicy expiration policy associated with this ticket + * @return The ticket granting ticket. + * @throws AbstractTicketException ticket exception thrown when generating the ticket + * @since 4.2 + */ + ProxyGrantingTicket grantProxyGrantingTicket(String id, + Authentication authentication, + ExpirationPolicy expirationPolicy) + throws Exception; +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicketImpl.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicketImpl.java index 1d323d8eb..7f667ad33 100644 --- a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicketImpl.java +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/ServiceTicketImpl.java @@ -18,6 +18,7 @@ package org.maxkey.authz.cas.endpoint.ticket; import org.apache.commons.lang3.builder.EqualsBuilder; +import org.maxkey.authz.cas.endpoint.ticket.proxy.ProxyGrantingTicket; import org.maxkey.domain.apps.AppsCasDetails; import org.springframework.security.core.Authentication; @@ -33,7 +34,7 @@ import javax.persistence.Column; * @since 3.0.0 */ -public class ServiceTicketImpl extends AbstractTicket { +public class ServiceTicketImpl extends AbstractTicket implements ServiceTicket{ private static final long serialVersionUID = -4223319704861765405L; @@ -112,4 +113,23 @@ public class ServiceTicketImpl extends AbstractTicket { .isEquals(); } + @Override + public Service getService() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isFromNewLogin() { + // TODO Auto-generated method stub + return false; + } + + @Override + public ProxyGrantingTicket grantProxyGrantingTicket(String id, Authentication authentication, + ExpirationPolicy expirationPolicy) throws Exception { + // TODO Auto-generated method stub + return null; + } + } diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicket.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicket.java new file mode 100644 index 000000000..0590778a5 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicket.java @@ -0,0 +1,115 @@ +package org.maxkey.authz.cas.endpoint.ticket; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.maxkey.domain.apps.AppsCasDetails; +import org.springframework.security.core.Authentication; + +/** + * Interface for a ticket granting ticket. A TicketGrantingTicket is the main + * access into the CAS service layer. Without a TicketGrantingTicket, a user of + * CAS cannot do anything. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public interface TicketGrantingTicket extends Ticket { + + /** + * The prefix to use when generating an id for a Ticket Granting Ticket. + */ + String PREFIX = "TGT"; + + /** + * Method to retrieve the authentication. + * + * @return the authentication + */ + Authentication getAuthentication(); + + /** + * Grant a ServiceTicket for a specific service. + * + * @param id The unique identifier for this ticket. + * @param service The service for which we are granting a ticket + * @param expirationPolicy the expiration policy. + * @param credentialProvided current credential event for issuing this ticket. Could be null. + * @param onlyTrackMostRecentSession track the most recent session by keeping the latest service ticket + * @return the service ticket granted to a specific service for the principal of the TicketGrantingTicket + */ + ServiceTicket grantServiceTicket(String id, Service service, + AppsCasDetails casDetails, + ExpirationPolicy expirationPolicy, + boolean credentialProvided, + boolean onlyTrackMostRecentSession); + + /** + * Gets an immutable map of service ticket and services accessed by this ticket-granting ticket. + * + * @return an immutable map of service ticket and services accessed by this ticket-granting ticket. + */ + Map getServices(); + + /** + * Gets proxy granting tickets created by this TGT. + * + * @return the proxy granting tickets + */ + Map getProxyGrantingTickets(); + + /** + * Remove all services of the TGT (at logout). + */ + void removeAllServices(); + + /** + * Convenience method to determine if the TicketGrantingTicket is the root + * of the hierarchy of tickets. + * + * @return true if it has no parent, false otherwise. + */ + boolean isRoot(); + + /** + * Gets the ticket-granting ticket at the root of the ticket hierarchy. + * + * @return Non -null root ticket-granting ticket. + */ + TicketGrantingTicket getRoot(); + + /** + * Gets all authentications ({@link #getAuthentication()} from this + * instance and all dependent tickets that reference this one. + * + * @return Non -null list of authentication associated with this ticket in leaf-first order. + */ + List getChainedAuthentications(); + + + /** + * Gets the service that produced a proxy-granting ticket. + * + * @return Service that produced proxy-granting ticket or null if this is not a proxy-granting ticket. + * @since 4.1 + */ + Service getProxiedBy(); + + /** + * Gets descendant tickets. These are generally ticket ids + * whose life-line is separate from the TGT until and unless + * the TGT goes away entirely. Things such as OAuth access tokens + * are a good example of such linked tickets. + * + * @return the descendant tickets + * @since 5.1 + */ + default Collection getDescendantTickets() { + return new HashSet<>(0); + } +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicketImpl.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicketImpl.java new file mode 100644 index 000000000..92d62b708 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketGrantingTicketImpl.java @@ -0,0 +1,198 @@ +package org.maxkey.authz.cas.endpoint.ticket; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.apache.commons.lang3.StringUtils; +import org.maxkey.domain.apps.AppsCasDetails; +import org.springframework.lang.NonNull; +import org.springframework.security.core.Authentication; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Concrete implementation of a TicketGrantingTicket. A TicketGrantingTicket is + * the global identifier of a principal into the system. It grants the Principal + * single-sign on access to any service that opts into single-sign on. + * Expiration of a TicketGrantingTicket is controlled by the ExpirationPolicy + * specified as object creation. + * + * @author Scott Battaglia + * @since 3.0.0 + */ +@Entity +@Table(name = "TICKETGRANTINGTICKET") +@DiscriminatorColumn(name = "TYPE") +@DiscriminatorValue(TicketGrantingTicket.PREFIX) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) + +public class TicketGrantingTicketImpl extends AbstractTicket implements TicketGrantingTicket { + + /** + * Unique Id for serialization. + */ + private static final long serialVersionUID = -8608149809180911599L; + + /** + * The authenticated object for which this ticket was generated for. + */ + @Lob + @Column(name = "AUTHENTICATION", nullable = false, length = Integer.MAX_VALUE) + private Authentication authentication; + + /** + * Service that produced a proxy-granting ticket. + */ + @Lob + @Column(name = "PROXIED_BY", length = Integer.MAX_VALUE) + private Service proxiedBy; + + /** + * The services associated to this ticket. + */ + @Lob + @Column(name = "SERVICES_GRANTED_ACCESS_TO", nullable = false, length = Integer.MAX_VALUE) + private HashMap services = new HashMap<>(); + + /** + * The {@link TicketGrantingTicket} this is associated with. + */ + @ManyToOne(targetEntity = TicketGrantingTicketImpl.class) + private TicketGrantingTicket ticketGrantingTicket; + + /** + * The PGTs associated to this ticket. + */ + @Lob + @Column(name = "PROXY_GRANTING_TICKETS", nullable = false, length = Integer.MAX_VALUE) + private HashMap proxyGrantingTickets = new HashMap<>(); + + /** + * The ticket ids which are tied to this ticket. + */ + @Lob + @Column(name = "DESCENDANT_TICKETS", nullable = false, length = Integer.MAX_VALUE) + private HashSet descendantTickets = new HashSet<>(); + + /** + * Constructs a new TicketGrantingTicket. + * May throw an {@link IllegalArgumentException} if the Authentication object is null. + * + * @param id the id of the Ticket + * @param proxiedBy Service that produced this proxy ticket. + * @param parentTicketGrantingTicket the parent ticket + * @param authentication the Authentication request for this ticket + * @param policy the expiration policy for this ticket. + */ + @JsonCreator + public TicketGrantingTicketImpl(@JsonProperty("id") final String id, @JsonProperty("proxiedBy") final Service proxiedBy, + @JsonProperty("ticketGrantingTicket") final TicketGrantingTicket parentTicketGrantingTicket, + @NonNull @JsonProperty("authentication") final Authentication authentication, @JsonProperty("expirationPolicy") final ExpirationPolicy policy) { + if (parentTicketGrantingTicket != null && proxiedBy == null) { + throw new IllegalArgumentException("Must specify proxiedBy when providing parent TGT"); + } + this.ticketGrantingTicket = parentTicketGrantingTicket; + this.authentication = authentication; + this.proxiedBy = proxiedBy; + } + + /** + * Constructs a new TicketGrantingTicket without a parent + * TicketGrantingTicket. + * + * @param id the id of the Ticket + * @param authentication the Authentication request for this ticket + * @param policy the expiration policy for this ticket. + */ + public TicketGrantingTicketImpl(final String id, final Authentication authentication, final ExpirationPolicy policy) { + this(id, null, null, authentication, policy); + } + + @Override + public synchronized ServiceTicket grantServiceTicket(final String id, final Service service, AppsCasDetails casDetails,final ExpirationPolicy expirationPolicy, + final boolean credentialProvided, final boolean onlyTrackMostRecentSession) { + final ServiceTicket serviceTicket = new ServiceTicketImpl(authentication,casDetails); + return serviceTicket; + } + + /** + * Normalize the path of a service by removing the query string and everything after a semi-colon. + * + * @param service the service to normalize + * @return the normalized path + + private static String normalizePath(final Service service) { + String path = service.getId(); + path = StringUtils.substringBefore(path, "?"); + path = StringUtils.substringBefore(path, ";"); + path = StringUtils.substringBefore(path, "#"); + return path; + } +*/ + /** + * Remove all services of the TGT (at logout). + */ + @Override + public void removeAllServices() { + this.services.clear(); + } + + + public String getPrefix() { + return TicketGrantingTicket.PREFIX; + } + + @Override + public Map getServices() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getProxyGrantingTickets() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Service getProxiedBy() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isRoot() { + // TODO Auto-generated method stub + return false; + } + + @Override + public TicketGrantingTicket getRoot() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getChainedAuthentications() { + // TODO Auto-generated method stub + return null; + } + + +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketState.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketState.java new file mode 100644 index 000000000..afd815ae7 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/TicketState.java @@ -0,0 +1,66 @@ +package org.maxkey.authz.cas.endpoint.ticket; + +import java.time.ZonedDateTime; + +import org.springframework.security.core.Authentication; + +/** + * @author Scott Battaglia + * @since 3.0.0 + */ +public interface TicketState { + + /** + * Returns the number of times a ticket was used. + * + * @return the number of times the ticket was used. + */ + int getCountOfUses(); + + /** + * Returns the last time the ticket was used. + * + * @return the last time the ticket was used. + */ + ZonedDateTime getLastTimeUsed(); + + /** + * Get the second to last time used. + * + * @return the previous time used. + */ + ZonedDateTime getPreviousTimeUsed(); + + /** + * Get the time the ticket was created. + * + * @return the creation time of the ticket. + */ + ZonedDateTime getCreationTime(); + + /** + * Authentication information from the ticket. This may be null. + * + * @return the authentication information. + */ + Authentication getAuthentication(); + + /** + * Method to retrieve the TicketGrantingTicket that granted this ticket. + * + * @return the ticket or null if it has no parent + */ + TicketGrantingTicket getTicketGrantingTicket(); + + /** + * Records the previous last time this ticket was used as well as + * the last usage time. The ticket usage count is also incremented. + *

Tickets themselves are solely responsible to maintain their state. The + * determination of ticket usage is left up to the implementation and + * the specific ticket type. + * + * @see ExpirationPolicy + * @since 5.0.0 + */ + void update(); +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyGrantingTicket.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyGrantingTicket.java new file mode 100644 index 000000000..ee7bade90 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyGrantingTicket.java @@ -0,0 +1,41 @@ +package org.maxkey.authz.cas.endpoint.ticket.proxy; + +import org.maxkey.authz.cas.endpoint.ticket.ExpirationPolicy; +import org.maxkey.authz.cas.endpoint.ticket.Service; +import org.maxkey.authz.cas.endpoint.ticket.TicketGrantingTicket; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Interface for a proxy granting ticket. A proxy-granting ticket is an opaque string that is + * used by a service to obtain proxy tickets for obtaining access to a back-end service on behalf of a client. + * Proxy-granting tickets are obtained from CAS upon validation of a service ticket or a proxy ticket. + * + * @author Misagh Moayyed + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +public interface ProxyGrantingTicket extends TicketGrantingTicket { + + /** The prefix to use when generating an id for a Proxy Granting Ticket. */ + String PROXY_GRANTING_TICKET_PREFIX = "PGT"; + + /** The prefix to use when generating an id for a Proxy Granting Ticket IOU. */ + String PROXY_GRANTING_TICKET_IOU_PREFIX = "PGTIOU"; + + /** + * Grant a proxy ticket for a specific service. + * + * @param id The unique identifier for this ticket. + * @param service The service for which we are granting a ticket + * @param expirationPolicy the expiration policy. + * @param onlyTrackMostRecentSession track the most recent session by keeping the latest service ticket + * @return the service ticket granted to a specific service for the + * principal of the TicketGrantingTicket + */ + ProxyTicket grantProxyTicket(String id, Service service, + ExpirationPolicy expirationPolicy, + boolean onlyTrackMostRecentSession); + +} + diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyTicket.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyTicket.java new file mode 100644 index 000000000..ae6bf8ce3 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/proxy/ProxyTicket.java @@ -0,0 +1,18 @@ +package org.maxkey.authz.cas.endpoint.ticket.proxy; + +import org.maxkey.authz.cas.endpoint.ticket.ServiceTicket; + +/** + * The {@link ProxyTicket} represents a CAS proxy ticket. A proxy ticket is an opaque string that a + * service uses as a credential to obtain access to a back-end service on behalf of a client. + * Proxy tickets are obtained from CAS upon a service’s + * presentation of a valid {@link ProxyGrantingTicket} + * and a service identifier for the back-end service to which it is connecting. + * + * @author Misagh Moayyed + * @since 4.2 + */ +public interface ProxyTicket extends ServiceTicket { + /** Proxy ticket prefix applied to unique ids. */ + String PROXY_TICKET_PREFIX = "PT"; +} diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RandomServiceTicketServices.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RandomServiceTicketServices.java index 3d1e758ea..67c59dede 100644 --- a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RandomServiceTicketServices.java +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RandomServiceTicketServices.java @@ -46,9 +46,9 @@ public abstract class RandomServiceTicketServices implements TicketServices { ticketId = generator.getNewTicketId(CasConstants.PREFIX.SERVICE_TICKET_PREFIX); }else if(ticket.getClass().getSimpleName().equalsIgnoreCase("ProxyTicketImpl")){ ticketId = generator.getNewTicketId(CasConstants.PREFIX.PROXY_TICKET_PREFIX); - }else if(ticket.getClass().getSimpleName().equalsIgnoreCase("ProxyTicketImpl")){ - ticketId = generator.getNewTicketId(CasConstants.PREFIX.PROXY_TICKET_PREFIX); - }else if(ticket.getClass().getSimpleName().equalsIgnoreCase("ProxyTicketImpl")){ + }else if(ticket.getClass().getSimpleName().equalsIgnoreCase("TicketGrantingTicketImpl")){ + ticketId = generator.getNewTicketId(CasConstants.PREFIX.TICKET_GRANTING_TICKET_PREFIX); + }else { ticketId = generator.getNewTicketId(CasConstants.PREFIX.PROXY_TICKET_PREFIX); } store(ticketId, ticket); diff --git a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RedisTicketServices.java b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RedisTicketServices.java index 9b2d98ad5..2a8a31bbe 100644 --- a/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RedisTicketServices.java +++ b/maxkey-protocols/maxkey-protocol-cas/src/main/java/org/maxkey/authz/cas/endpoint/ticket/service/RedisTicketServices.java @@ -28,7 +28,7 @@ public class RedisTicketServices extends RandomServiceTicketServices { RedisConnectionFactory connectionFactory; - public static String PREFIX="REDIS_CAS_SERVICE_TICKET_"; + public static String PREFIX="REDIS_CAS_TICKET_"; /** * @param connectionFactory */ diff --git a/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/CasRestClient.java b/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/CasRestClient.java new file mode 100644 index 000000000..55348b714 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/CasRestClient.java @@ -0,0 +1,132 @@ +package org.maxkey.web.authorize.endpoint; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; + +import javax.net.ssl.HttpsURLConnection; + +public class CasRestClient { + + + public static void main(String... args) throws Exception + { + String username ="admin"; + String password ="maxkey"; + validateFromCAS(username,password); + } + + public static boolean validateFromCAS(String username, String password) throws Exception + { + + String url = "https://sso.maxkey.top/maxkey/authz/cas/v1/tickets"; + try + { + org.maxkey.client.utils.HttpsTrusts.beforeConnection(); + HttpsURLConnection hsu = (HttpsURLConnection)openConn(url); + String s = URLEncoder.encode("username","UTF-8") + "=" + URLEncoder.encode(username,"UTF-8"); + s+="&" +URLEncoder.encode("password","UTF-8") + "=" + URLEncoder.encode(password,"UTF-8"); + + System.out.println(s); + OutputStreamWriter out = new OutputStreamWriter(hsu.getOutputStream()); + BufferedWriter bwr = new BufferedWriter(out); + bwr.write(s); + bwr.flush(); + bwr.close(); + out.close(); + + String tgt = hsu.getHeaderField("location"); + System.out.println( hsu.getResponseCode()); + if(tgt != null && hsu.getResponseCode() == 201) + { + System.out.println(tgt); + + System.out.println("Tgt is : " + tgt.substring( tgt.lastIndexOf("/") +1)); + tgt = tgt.substring( tgt.lastIndexOf("/") +1); + bwr.close(); + closeConn(hsu); + + + String serviceURL = "http://cas.demo.maxkey.top:8080/demo-cas/"; + String encodedServiceURL = URLEncoder.encode("service","utf-8") +"=" + URLEncoder.encode(serviceURL,"utf-8"); + System.out.println("Service url is : " + encodedServiceURL); + + + + String myURL = url+ "/"+ tgt ; + System.out.println(myURL); + hsu = (HttpsURLConnection)openConn(myURL); + out = new OutputStreamWriter(hsu.getOutputStream()); + bwr = new BufferedWriter(out); + bwr.write(encodedServiceURL); + bwr.flush(); + bwr.close(); + out.close(); + + System.out.println("Response code is: " + hsu.getResponseCode()); + + BufferedReader isr = new BufferedReader( new InputStreamReader(hsu.getInputStream())); + String line; + System.out.println( hsu.getResponseCode()); + while ((line = isr.readLine()) != null) { + System.out.println( line); + } + isr.close(); + hsu.disconnect(); + return true; + + } + else + { + return false; + } + + + } + catch(MalformedURLException mue) + { + mue.printStackTrace(); + throw mue; + + } + catch(IOException ioe) + { + ioe.printStackTrace(); + throw ioe; + } + + + + + + } + + + static URLConnection openConn(String urlk) throws MalformedURLException, IOException + { + + URL url = new URL(urlk); + HttpsURLConnection hsu = (HttpsURLConnection) url.openConnection(); + hsu.setDoInput(true); + hsu.setDoOutput(true); + hsu.setRequestMethod("POST"); + return hsu; + + + } + + + static void closeConn(HttpsURLConnection c) + { + c.disconnect(); + } + + + } + diff --git a/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/RestTestClient.java b/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/RestTestClient.java new file mode 100644 index 000000000..0c6c9d1e9 --- /dev/null +++ b/maxkey-protocols/maxkey-protocol-cas/src/test/java/org/maxkey/web/authorize/endpoint/RestTestClient.java @@ -0,0 +1,51 @@ +package org.maxkey.web.authorize.endpoint; +/* +import org.pac4j.cas.profile.CasRestProfile; +import org.pac4j.cas.client.rest.CasRestFormClient; +import org.pac4j.cas.config.CasConfiguration; +import org.pac4j.cas.credentials.authenticator.CasRestAuthenticator; +import org.pac4j.cas.profile.CasProfile; +import org.pac4j.core.context.JEEContext; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.credentials.TokenCredentials; +import org.pac4j.core.credentials.UsernamePasswordCredentials; +import org.pac4j.core.exception.HttpAction; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.Map; +import java.util.Set; + +public class RestTestClient { + + public static void main(String[] args ) throws HttpAction { + final String casUrlPrefix = "http://localhost:8080/cas"; + String username = args[0]; + String password = args[1]; + String serviceUrl = args[2]; + CasConfiguration casConfiguration = new CasConfiguration(casUrlPrefix); + final CasRestAuthenticator authenticator = new CasRestAuthenticator(casConfiguration); + final CasRestFormClient client = new CasRestFormClient(casConfiguration,"username","password"); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final WebContext webContext = new JEEContext(request, response); + casConfiguration.init(webContext); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username,password,"testclient"); + CasRestAuthenticator restAuthenticator = new CasRestAuthenticator(casConfiguration); + // authenticate with credentials (validate credentials) + restAuthenticator.validate(credentials, webContext); + final CasRestProfile profile = (CasRestProfile) credentials.getUserProfile(); + // get service ticket + final TokenCredentials casCredentials = client.requestServiceTicket(serviceUrl, profile, webContext); + // validate service ticket + final CasProfile casProfile = client.validateServiceTicket(serviceUrl, casCredentials, webContext); + Map attributes = casProfile.getAttributes(); + Set> mapEntries = attributes.entrySet(); + for (Map.Entry entry : mapEntries) { + System.out.println(entry.getKey() + ":" + entry.getValue()); + } + client.destroyTicketGrantingTicket(profile,webContext); + } + +}*/ diff --git a/maxkey-web-maxkey/src/main/java/org/maxkey/MaxKeyMvcConfig.java b/maxkey-web-maxkey/src/main/java/org/maxkey/MaxKeyMvcConfig.java index 2ef03ba10..798bf2edb 100644 --- a/maxkey-web-maxkey/src/main/java/org/maxkey/MaxKeyMvcConfig.java +++ b/maxkey-web-maxkey/src/main/java/org/maxkey/MaxKeyMvcConfig.java @@ -112,6 +112,9 @@ public class MaxKeyMvcConfig implements WebMvcConfigurer { //cas3.0 Validate .excludePathPatterns("/authz/cas/p3/serviceValidate") .excludePathPatterns("/authz/cas/p3/proxyValidate") + //rest + .excludePathPatterns("/authz/cas/v1/tickets") + .excludePathPatterns("/authz/cas/v1/tickets/*") //OAuth .addPathPatterns("/oauth/v20/authorize") -- GitLab