Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bpipe authentication with an application name #288

Merged
merged 17 commits into from
Mar 26, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ beqs_Impl <- function(con, screenName, screenType, group, pitdate, languageId, v
.Call(`_Rblpapi_beqs_Impl`, con, screenName, screenType, group, pitdate, languageId, verbose)
}

blpConnect_Impl <- function(host, port) {
.Call(`_Rblpapi_blpConnect_Impl`, host, port)
blpConnect_Impl <- function(host, port, app_name_) {
.Call(`_Rblpapi_blpConnect_Impl`, host, port, app_name_)
}

#' This function retrieves the version of Bloomberg API headers.
Expand Down
17 changes: 12 additions & 5 deletions R/blpAuthenticate.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
##' This function authenticates against the the Bloomberg API
##'
##' @title Authenticate Bloomberg API access
##' @param uuid A character variable with a unique user id token
##' @param uuid A character variable with a unique user id token. If this
##' is missing the function will attempt to connect to bpipe using the connection. It
##' is assumed that an app_name was set. See blpConnect() for app_name information
##' @param host A character variable with a hostname, defaults to 'localhost'
##' @param ip.address An optional character variable with an IP address
##' @param con A connection object as created by a \code{blpConnect}
##' call, and retrieved via the internal function
##' call, and retrieved via the internal function. This is the only required
##' argument to authenticate a bpipe connection with a app_name.
##' \code{defaultConnection}.
##' @return The returned object should be passed to subsequent data
##' calls via bdp(), bds(), etc.
##' @return The returned object should be passed to subsequent data
##' calls via bdp(), bds(), etc.
##' @author Whit Armstrong and Dirk Eddelbuettel
##' @examples
##' \dontrun{
Expand All @@ -44,7 +47,11 @@ blpAuthenticate <- function(uuid, host="localhost", ip.address, con=defaultConne
ignore.stdout=FALSE, ignore.stderr=FALSE,wait=TRUE)
ip.address <- strsplit(cmd.res,"address ")[[1]][2]
}
authenticate_Impl(con, as.character(uuid), ip.address)
if(missing(uuid)) {
authenticate_Impl(con, NULL, NULL)
} else {
authenticate_Impl(con, as.character(uuid), ip.address)
}
}

#### TODO: rename to just 'authenticate' ?
Expand Down
13 changes: 10 additions & 3 deletions R/blpConnect.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
##' @param host A character option with either a machine name that is
##' resolvable by DNS, or an IP address. Defaults to
##' \sQuote{localhost}.
##' @param app_name the name of an application that is authorized
##' to connect to bpipe
##' @return In the \code{default=TRUE} case nothing is returned, and
##' this connection is automatically used for all future calls which
##' omit the \code{con} argument. Otherwise a connection object is
Expand All @@ -45,18 +47,23 @@
##' environment. This effectively frees users from having to
##' explicitly create such an object.
##' @author Whit Armstrong and Dirk Eddelbuettel
##' @seealso Many SAPI and bPipe connections require authentication
##' @seealso Many SAPI and bPipe connections require authentication
##' via \code{blpAuthenticate} after \code{blpConnect}.
##' @examples
##' \dontrun{
##' con <- blpConnect() # adjust as needed
##' }
blpConnect <- function(host=getOption("blpHost", "localhost"),
port=getOption("blpPort", 8194L),
default=TRUE) {
default=TRUE,
app_name = getOption("blpAppName", NULL)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistic nitpick: we should use something other than snake_case for argument names. We've tended toward camelCase in most functions, thought there are also a few old-school R dot.separated names left over. I think appName fits best here.

if (storage.mode(port) != "integer") port <- as.integer(port)
if (storage.mode(host) != "character") stop("Host argument must be character.", call.=FALSE)
con <- blpConnect_Impl(host, port)
if(is.null(app_name)) {
con <- blpConnect_Impl(host, port, NULL)
} else {
con <- blpConnect_Impl(host, port, app_name)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the if (is.null(...)) and else branches could be collapsed into one which might be simpler. Yet this is clear.

Or it might be better to change it to two lines including one comment that null/non-null is handled at the C(++) level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good point. I can make the change on Monday. I like the simplification with a note in the documentation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! The code is in great shape but I want to hear from @johnlaing too.


if (default) .pkgenv$con <- con else return(con)
}
9 changes: 6 additions & 3 deletions man/blpAuthenticate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions man/blpConnect.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions src/RcppExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,15 @@ BEGIN_RCPP
END_RCPP
}
// blpConnect_Impl
SEXP blpConnect_Impl(const std::string host, const int port);
RcppExport SEXP _Rblpapi_blpConnect_Impl(SEXP hostSEXP, SEXP portSEXP) {
SEXP blpConnect_Impl(const std::string host, const int port, SEXP app_name_);
RcppExport SEXP _Rblpapi_blpConnect_Impl(SEXP hostSEXP, SEXP portSEXP, SEXP app_name_SEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< const std::string >::type host(hostSEXP);
Rcpp::traits::input_parameter< const int >::type port(portSEXP);
rcpp_result_gen = Rcpp::wrap(blpConnect_Impl(host, port));
Rcpp::traits::input_parameter< SEXP >::type app_name_(app_name_SEXP);
rcpp_result_gen = Rcpp::wrap(blpConnect_Impl(host, port, app_name_));
return rcpp_result_gen;
END_RCPP
}
Expand Down Expand Up @@ -253,7 +254,7 @@ static const R_CallMethodDef CallEntries[] = {
{"_Rblpapi_bds_Impl", (DL_FUNC) &_Rblpapi_bds_Impl, 7},
{"_Rblpapi_getPortfolio_Impl", (DL_FUNC) &_Rblpapi_getPortfolio_Impl, 7},
{"_Rblpapi_beqs_Impl", (DL_FUNC) &_Rblpapi_beqs_Impl, 7},
{"_Rblpapi_blpConnect_Impl", (DL_FUNC) &_Rblpapi_blpConnect_Impl, 2},
{"_Rblpapi_blpConnect_Impl", (DL_FUNC) &_Rblpapi_blpConnect_Impl, 3},
{"_Rblpapi_getHeaderVersion", (DL_FUNC) &_Rblpapi_getHeaderVersion, 0},
{"_Rblpapi_getRuntimeVersion", (DL_FUNC) &_Rblpapi_getRuntimeVersion, 0},
{"_Rblpapi_bsrch_Impl", (DL_FUNC) &_Rblpapi_bsrch_Impl, 4},
Expand Down
106 changes: 99 additions & 7 deletions src/authenticate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// authenticate.cpp -- Function to authenticate to Bloomberg backend
//
// Copyright (C) 2013 Whit Armstrong
// Copyright (C) 2015 Whit Armstrong and Dirk Eddelbuettel
// Copyright (C) 2015 Whit Armstrong and Dirk Eddelbuettelp
//
// This file is part of Rblpapi
//
Expand All @@ -22,6 +22,12 @@


#include <string>
#include <blpapi_defs.h>
#include <blpapi_element.h>
#include <blpapi_eventdispatcher.h>
#include <blpapi_exception.h>
#include <blpapi_name.h>
#include <blpapi_request.h>
#include <blpapi_session.h>
#include <blpapi_event.h>
#include <blpapi_message.h>
Expand All @@ -36,6 +42,8 @@ using BloombergLP::blpapi::Request;
using BloombergLP::blpapi::Event;
using BloombergLP::blpapi::Message;
using BloombergLP::blpapi::MessageIterator;
using BloombergLP::blpapi::CorrelationId;
using BloombergLP::blpapi::EventQueue;

static void identityFinalizer(SEXP identity_) {
Identity* identity = reinterpret_cast<Identity*>(R_ExternalPtrAddr(identity_));
Expand All @@ -45,11 +53,7 @@ static void identityFinalizer(SEXP identity_) {
}
}

// Simpler interface
//
// [[Rcpp::export]]
SEXP authenticate_Impl(SEXP con_, SEXP uuid_, SEXP ip_address_) {

Identity* authenticateWithId(SEXP con_, SEXP uuid_, SEXP ip_address_) {
// via Rcpp Attributes we get a try/catch block with error propagation to R "for free"
Session* session =
reinterpret_cast<Session*>(checkExternalPointer(con_, "blpapi::Session*"));
Expand Down Expand Up @@ -93,5 +97,93 @@ SEXP authenticate_Impl(SEXP con_, SEXP uuid_, SEXP ip_address_) {
}
if (event.eventType() == Event::RESPONSE) { break; }
}
return createExternalPointer<Identity>(identity_p,identityFinalizer,"blpapi::Identity*");
return identity_p;
}

Identity* authenticateWithApp(SEXP con_) {
Identity* identity_p = 0;

// via Rcpp Attributes we get a try/catch block with error propagation to R "for free"
Session* session =
reinterpret_cast<Session*>(checkExternalPointer(con_, "blpapi::Session*"));

// setup authorization service
std::string service("//blp/apiauth");

// authorize
if (session->openService(service.c_str())) {
Service authService = session->getService(service.c_str());
CorrelationId correlation_id(10);
std::string token;
EventQueue tokenEventQueue;
session->generateToken(correlation_id, &tokenEventQueue);
Event event = tokenEventQueue.nextEvent();
//
// get token for session
//
if(event.eventType() == Event::TOKEN_STATUS ||
event.eventType() == Event::REQUEST_STATUS) {
MessageIterator msgIter(event);
while(msgIter.next()) {
Message msg = msgIter.message();
if(msg.messageType() == "TokenGenerationSuccess") {
token = msg.getElementAsString("token");
}
else if(msg.messageType() == "TokenGenerationFailure") {
Rcpp::stop("Failed to generate token");
}
}
}

//
// begin authorization
//
if(!token.empty()) {
Request authRequest = authService.createAuthorizationRequest();
authRequest.set("token", token.c_str());
identity_p = new Identity(session->createIdentity());
session->sendAuthorizationRequest(authRequest, identity_p);
// parse messages
bool message_found = false;
while(!message_found) {
Event event = session->nextEvent(100000);
if(event.eventType() == Event::RESPONSE ||
event.eventType() == Event::REQUEST_STATUS ||
event.eventType() == Event::PARTIAL_RESPONSE) {
MessageIterator msgIter(event);
while(msgIter.next()) {
Message msg = msgIter.message();
if(msg.messageType() == "AuthorizationSuccess") {
message_found = true;
}
else {
Rcpp::stop(">>> Failed to Authorize");
}
}
}
else if(event.eventType() == Event::TIMEOUT) {
Rcpp::stop("Timed out trying to authorize");
}
}
}
else {
Rcpp::stop("Generated token was empty");
}
}
return identity_p;
}

// Simpler interface
//
// [[Rcpp::export]]
SEXP authenticate_Impl(SEXP con_, SEXP uuid_, SEXP ip_address_) {
Identity* identity_p = NULL;
if(uuid_ == R_NilValue) {
identity_p = authenticateWithApp(con_);
}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we put our else on the same line but hey...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More than happy to follow your preferred format. Since I am going to make the change above I will make the change here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the 'Outdated' tag :) While I was at it I pushed some trivial changes back to your branch. So make sure you pull on Monday.

identity_p = authenticateWithId(con_, uuid_, ip_address_);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the simpler switch here and the hand-off to these two specialised functions with the old default and new add-on. Nicely done.

if(identity_p == NULL) { Rcpp::stop("Identity pointer is null\n"); }
return createExternalPointer<Identity>(identity_p, identityFinalizer, "blpapi::Identity*");
}
41 changes: 38 additions & 3 deletions src/blpConnect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
using BloombergLP::blpapi::Session;
using BloombergLP::blpapi::SessionOptions;

const std::string APP_PREFIX("AuthenticationMode=APPLICATION_ONLY;"
"ApplicationAuthenticationType=APPNAME_AND_KEY;"
"ApplicationName=");

static void sessionFinalizer(SEXP session_) {
Session* session = reinterpret_cast<Session*>(R_ExternalPtrAddr(session_));
if (session) {
Expand All @@ -36,16 +40,47 @@ static void sessionFinalizer(SEXP session_) {
}
}

// [[Rcpp::export]]
SEXP blpConnect_Impl(const std::string host, const int port) {
Session* blpConnectWithApp(const std::string host, const int port, const std::string app_name) {
SessionOptions sessionOptions;
sessionOptions.setServerHost(host.c_str());
sessionOptions.setServerPort(port);
std::string app(app_name);
std::string authentication_string = APP_PREFIX + app;
sessionOptions.setAuthenticationOptions(authentication_string.c_str());
Session* sp = new Session(sessionOptions);

if (!sp->start()) {
Rcpp::stop("Failed to start session with bpipe app name.\n");
}

return sp;
}
Session* blpConnectNoApp(const std::string host, const int port) {
SessionOptions sessionOptions;
sessionOptions.setServerHost(host.c_str());
sessionOptions.setServerPort(port);
Session* sp = new Session(sessionOptions);

if (!sp->start()) {
Rcpp::stop("Failed to start session.\n");
Rcpp::stop("Failed to start session without an app.\n");
}

return sp;
}

// [[Rcpp::export]]
SEXP blpConnect_Impl(const std::string host, const int port, SEXP app_name_) {
Session* sp = NULL;
if(app_name_ == NULL) {
sp = blpConnectNoApp(host, port);
}
else {
std::string app_name = Rcpp::as<std::string>(app_name_);
sp = blpConnectWithApp(host, port, app_name);
}
if(sp == NULL) {
Rcpp::stop("Session pointer is NULL\n");
}
return createExternalPointer<Session>(sp, sessionFinalizer, "blpapi::Session*");
}