blob: 51240279b34af9c37b61fc725d03b94746c40f9c [file] [log] [blame]
/*
* Copyright 2018, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package ext.maps.booking.partner.v2;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.AvailabilityUpdate;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.Booking;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.BookingStatus;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityRequest;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityResponse;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityResponse.DurationRequirement;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CreateBookingRequest;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CreateBookingResponse;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.GetBookingStatusRequest;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.GetBookingStatusResponse;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.ListBookingsRequest;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.ListBookingsResponse;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.PrepaymentStatus;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.Slot;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UpdateBookingRequest;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UpdateBookingResponse;
import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UserPaymentOption;
import grpc.health.v1.Health.HealthImpl;
import io.grpc.Grpc;
import io.grpc.Metadata;
import io.grpc.Server;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.ServerInterceptors;
import io.grpc.Status;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.stub.StreamObserver;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
/** Booking Server with TLS enabled */
public class BookingService {
private static final Logger logger = Logger.getLogger(BookingService.class.getName());
private Server server;
private final String host;
private final int port;
private final String certChainFilePath;
private final String privateKeyFilePath;
private final String trustedrootsFilePath;
private final Set<String> acceptedCNs;
public BookingService(
String host,
int port,
String certChainFilePath,
String privateKeyFilePath,
String trustedrootsFilePath,
Set<String> acceptedCNs) {
this.host = host;
this.port = port;
this.certChainFilePath = certChainFilePath;
this.privateKeyFilePath = privateKeyFilePath;
this.trustedrootsFilePath = trustedrootsFilePath;
this.acceptedCNs = acceptedCNs;
}
private SslContextBuilder getSslContextBuilder() {
SslContextBuilder sslClientContextBuilder =
SslContextBuilder.forServer(new File(certChainFilePath), new File(privateKeyFilePath));
if (trustedrootsFilePath != null) {
sslClientContextBuilder.trustManager(new File(trustedrootsFilePath));
sslClientContextBuilder.clientAuth(ClientAuth.OPTIONAL);
}
return GrpcSslContexts.configure(sslClientContextBuilder, SslProvider.OPENSSL);
}
private void start() throws IOException {
server =
NettyServerBuilder.forAddress(new InetSocketAddress(host, port))
.addService(ServerInterceptors.intercept(new BookingServiceImpl(), new MyInterceptor()))
.addService(new HealthImpl())
.sslContext(getSslContextBuilder().build())
.build()
.start();
logger.info("Booking Server started, listening on " + port);
Runtime.getRuntime()
.addShutdownHook(
new Thread() {
@Override
public void run() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
BookingService.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/** Main launches the server. */
public static void main(String[] args) throws IOException, InterruptedException {
// Set the accepted cns of the server
Set<String> acceptedCNs = new HashSet<String>();
acceptedCNs.add("mapsbooking.businesslink-3.net");
// TODO(partner): override your host name, port, server_cert_chain file path,
// private_key file path and trusted_client_roots file path here to build the server
final BookingService server =
new BookingService(
"localhost",
8443,
"src/main/certificates/server_cert_chain.pem",
"src/main/certificates/server_private_key.pem",
"src/main/certificates/trusted_client_roots.pem",
acceptedCNs);
server.start();
server.blockUntilShutdown();
}
/**
* Implement the server interceptor to do peer certificate validation that checks for a specific
* CN in the subject name, if the CN check fails, the connection will be closed with
* "PERMISSION_DENIED" status message.
*/
private class MyInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
SSLSession sslSession = call.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
try {
Certificate[] peerCerts = sslSession.getPeerCertificates();
if (peerCerts[0] instanceof java.security.cert.X509Certificate) {
X509Certificate peerCert = (X509Certificate) peerCerts[0];
X500Name x500Name = new JcaX509CertificateHolder(peerCert).getSubject();
RDN cn = x500Name.getRDNs(BCStyle.CN)[0];
String peerCN = IETFUtils.valueToString(cn.getFirst().getValue());
if (!acceptedCNs.contains(peerCN)) {
throw new SSLPeerUnverifiedException("Invalid CN");
}
} else {
throw new SSLPeerUnverifiedException("Invalid certificate type");
}
} catch (SSLPeerUnverifiedException se) {
System.err.println(se.getMessage());
call.close(Status.PERMISSION_DENIED, headers);
return new ServerCall.Listener<ReqT>() {};
} catch (CertificateEncodingException ce) {
System.err.println(ce.getMessage());
call.close(Status.PERMISSION_DENIED, headers);
return new ServerCall.Listener<ReqT>() {};
}
return next.startCall(call, headers);
}
}
/** The implementation of Booking Service. */
static class BookingServiceImpl extends BookingServiceGrpc.BookingServiceImplBase {
@Override
public void checkAvailability(
CheckAvailabilityRequest request,
StreamObserver<CheckAvailabilityResponse> responseObserver) {
Slot requestedSlot = request.getSlot();
// TODO(partner): perform availability check
//
// Error conditions: response with corresponding canonical gRPC error code
// example:
// responseObserver.onError(
// new StatusException(
// io.grpc.Status.INVALID_ARGUMENT.withDescription("Invalid merchant id")
// )
// );
//
// Happy path: implement the helper functions, populate the response
CheckAvailabilityResponse response =
CheckAvailabilityResponse.newBuilder()
.setSlot(requestedSlot)
.setCountAvailable(getAvailableSpots(requestedSlot))
.setDurationRequirement(getDurationRequirement(requestedSlot)) // optional
.setAvailabilityUpdate(getAvailabilityUpdate(requestedSlot)) // optional
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private int getAvailableSpots(Slot slot) {
// TODO(partner): get the available spots for the given slot
//
// int available_spots = ...
// return available_spots;
throw new UnsupportedOperationException("Not implemented yet");
}
private DurationRequirement getDurationRequirement(Slot slot) {
// TODO(partner): optional, get the duration requirment for the given slot
//
// DurationRequirement durationRequirement = ...
// return durationRequirement;
throw new UnsupportedOperationException("Not implemented yet");
}
private AvailabilityUpdate getAvailabilityUpdate(Slot slot) {
// TODO(partner): optional, get the availability update for the given slot
//
// AvailabilityUpdate availabilityUpdate = ...
// return availabilityUpdate;
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void createBooking(
CreateBookingRequest request, StreamObserver<CreateBookingResponse> responseObserver) {
// TODO(partner): create a booking based on the request
//
// Error conditions:
// unexpected error -> response with corresponding canonical gRPC error code
// example:
// responseObserver.onError(
// new StatusException(
// io.grpc.Status.INVALID_ARGUMENT.withDescription("Invalid merchant id")
// )
// );
// business logic error -> set the BookingFailure in the response
// example:
// BookingFailure bookingFailure = BookingFailure.newBuilder()
// .setCause(Cause.SLOT_UNAVAILABLE)
// .setDescription("slot is not available now")
// .build();
// CreateBookingResponse response = CreateBookingResponse.newBuilder()
// .setBookingFailure(bookingFailure)...
//
// Happy path: implement the helper functions, populate the response
Booking booking = createSuccessfulBooking(request);
CreateBookingResponse response =
CreateBookingResponse.newBuilder()
.setBooking(booking)
.setUserPaymentOption(getUserPaymentOption(booking)) // optional
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private Booking createSuccessfulBooking(CreateBookingRequest request) {
// TODO(partner): create a booking based on the create booking request and return the
// created booking instance
//
// Booking created = ...(e.g.
// Booking.newBuilder()
// .setBookingId("sampleBooking1")
// .setSlot(request.getSlot())
// .setUserInformation(request.getUserInformation())
// .setStatus(BookingStatus.CONFIRMED)
// .setPaymentInformation(request.getPaymentInformation())
// .build()
// )
// return created;
throw new UnsupportedOperationException("Not implemented yet");
}
private UserPaymentOption getUserPaymentOption(Booking booking) {
// TODO(partner): optional, get the user payment option for the given booking
//
// UserPaymentOption paymentOption = ...
// return paymentOption;
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void updateBooking(
UpdateBookingRequest request, StreamObserver<UpdateBookingResponse> responseObserver) {
// TODO(partner): update the booking based on the request
//
// Error conditions:
// unexpected error -> response with corresponding canonical gRPC error code
// example:
// responseObserver.onError(
// new StatusException(
// io.grpc.Status.NOT_FOUND.withDescription("Unknown booking id")
// )
// );
// business logic error -> set the BookingFailure in the response
// example:
// BookingFailure bookingFailure = BookingFailure.newBuilder()
// .setCause(Cause.SLOT_UNAVAILABLE)
// .setDescription("slot is not available now")
// .build();
// CreateBookingResponse response = CreateBookingResponse.newBuilder()
// .setBookingFailure(bookingFailure)...
//
// Happy path: implement the helper functions, populate the response
Booking booking = updateSuccessfulBooking(request);
UpdateBookingResponse response =
UpdateBookingResponse.newBuilder()
.setBooking(booking)
.setUserPaymentOption(getUserPaymentOption(booking)) // option
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private Booking updateSuccessfulBooking(UpdateBookingRequest request) {
// TODO(partner): look up the booking with bookingId, update fields according to
// updateMask and return the updated booking instance
//
// String bookingId = request.getBooking().getBookingId();
// FieldMask updateMask = request.getUpdateMask();
// Booking updated = ...
// return updated;
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void getBookingStatus(
GetBookingStatusRequest request,
StreamObserver<GetBookingStatusResponse> responseObserver) {
String bookingId = request.getBookingId();
// TODO(partner): look up the booking status and prepayment status with bookingId
//
// Error conditions:
// unexpected error -> response with corresponding canonical gRPC error code
// example:
// responseObserver.onError(
// new StatusException(
// io.grpc.Status.NOT_FOUND.withDescription("Unknown booking id")
// )
// );
//
// Happy path: implement the helper functions, populate the response
GetBookingStatusResponse response =
GetBookingStatusResponse.newBuilder()
.setBookingId(bookingId)
.setBookingStatus(getBookingStatus(bookingId))
.setPrepaymentStatus(getPrepaymentStatus(bookingId))
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private BookingStatus getBookingStatus(String bookingId) {
// TODO(partner): get the booking status for the given bookingId
//
// BookingStatus bookingStatus = ...
// return bookingStatus;
throw new UnsupportedOperationException("Not implemented yet");
}
private PrepaymentStatus getPrepaymentStatus(String bookingId) {
// TODO(partner): get the prepayment status for the given bookingId
//
// Prepayment prepaymentStatus = ...
// return prepaymentStatus;
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void listBookings(
ListBookingsRequest request, StreamObserver<ListBookingsResponse> responseObserver) {
String userId = request.getUserId();
// TODO(partner): look up all bookings with the given userId
//
// Error conditions:
// unexpected error -> response with corresponding canonical gRPC error code
// example:
// responseObserver.onError(
// new StatusException(
// io.grpc.Status.NOT_FOUND.withDescription("Unknown user id")
// )
// );
//
// Happy path: implement the helper function, populate the response
ListBookingsResponse response =
ListBookingsResponse.newBuilder().addAllBookings(getListBooking(userId)).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private List<Booking> getListBooking(String userId) {
// TODO(partner): get the bookings list for the given userId
//
// List<Booking> bookings = ...
// return bookings;
throw new UnsupportedOperationException("Not implemented yet");
}
}
}