Skip to content

Commit

Permalink
Merge pull request titi38#23 from jcourtat/add_auth_bearer
Browse files Browse the repository at this point in the history
Add authorization via bearer
  • Loading branch information
titi38 authored Sep 1, 2017
2 parents bd1634b + 617a739 commit f6dad35
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 8 deletions.
59 changes: 59 additions & 0 deletions examples/6_authorization/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
##############################
# EXAMPLE #
# created by T.DESCOMBES #
# created by J.COURTAT #
# 2017 #
##############################

UNAME := $(shell uname)

ifeq ($(UNAME), Linux)
OS = LINUX
else ifeq ($(UNAME), Darwin)
OS = MACOSX
else
OS = OTHER
endif

LIB_DIR = lib
CXX = g++

ifeq ($(OS),MACOSX)
LIBS = -lnavajo -L../../$(LIB_DIR) -lz $(shell pkg-config --libs-only-L libjwt) $(shell pkg-config --libs-only-l libjwt)
DEFS = -D__darwin__ -D__x86__ -fPIC -fno-common -D_REENTRANT
CXXFLAGS = -O3 -Wdeprecated-declarations
else
LIBS = -lnavajo -L../../$(LIB_DIR) -lz $(shell pkg-config --libs-only-L libjwt) $(shell pkg-config --libs-only-l libjwt) -pthread
DEFS = -DLINUX -Wall -Wno-unused -fexceptions -fPIC -D_REENTRANT
CXXFLAGS = -O3 -Wdeprecated-declarations
endif

CPPFLAGS = -I. \
$(shell pkg-config --cflags libjwt)\
-I../../include \
-I../../MPFDParser-1.1.1

LD = g++

LDFLAGS = -Wall -Wno-unused -O4

EXAMPLE_NAME = example

EXAMPLE_OBJS = \
example.o

#######################
# DEPENDENCE'S RULES #
#######################

%.o: %.cc
$(CXX) -c $< -o $@ $(CXXFLAGS) $(CPPFLAGS) $(DEFS)

all: $(EXAMPLE_NAME)

$(EXAMPLE_NAME): $(EXAMPLE_OBJS) $(LIB_STATIC_NAME)
rm -f $@
$(LD) $(LDFLAGS) -o $@ $(EXAMPLE_OBJS) $(LIB_STATIC_NAME) $(LIBS)

clean:
@for i in $(EXAMPLE_OBJS); do rm -f $$i ; done
Binary file added examples/6_authorization/example
Binary file not shown.
40 changes: 39 additions & 1 deletion include/libnavajo/WebServer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class WebServer
int s_server_session_id_context;
static char *certpass;

int (*tokDecodeCallback) (const std::string& tokb64, std::string& secret, std::string& decoded);
time_t (*authBearTokDecExpirationCb) (std::string& tokenDecoded);
int (*authBearTokDecScopesCb) (const std::string& tokenDecoded, const std::string& resourceUrl, std::string& errDescr);
std::string authBearerRealm;
bool authBearerEnabled;
std::string tokDecodeSecret;
std::queue<ClientSockData *> clientsQueue;
pthread_cond_t clientsQueue_cond;
pthread_mutex_t clientsQueue_mutex;
Expand All @@ -50,12 +56,15 @@ class WebServer
static int password_cb(char *buf, int num, int rwflag, void *userdata);

bool isUserAllowed(const std::string &logpassb64, std::string &username);
bool isTokenAllowed(const std::string &tokb64,
const std::string &resourceUrl,
std::string &respHeader);
bool isAuthorizedDN(const std::string str);

size_t recvLine(int client, char *bufLine, size_t);
bool accept_request(ClientSockData* client, bool authSSL);
void fatalError(const char *);
static std::string getHttpHeader(const char *messageType, const size_t len=0, const bool keepAlive=true, const bool zipped=false, HttpResponse* response=NULL);
static std::string getHttpHeader(const char *messageType, const size_t len=0, const bool keepAlive=true, const char *authBearerAdditionalHeaders=NULL, const bool zipped=false, HttpResponse* response=NULL);
static const char* get_mime_type(const char *name);
u_short init();

Expand All @@ -82,9 +91,12 @@ class WebServer
volatile size_t nbServerSock;

const static char authStr[];
const static char authBearerStr[];

std::map<std::string,time_t> usersAuthHistory;
pthread_mutex_t usersAuthHistory_mutex;
std::map<std::string,time_t> tokensAuthHistory;
pthread_mutex_t tokensAuthHistory_mutex;
std::map<IpAddress,time_t> peerIpHistory;
std::map<std::string,time_t> peerDnHistory;
pthread_mutex_t peerDnHistory_mutex;
Expand Down Expand Up @@ -192,6 +204,32 @@ class WebServer
*/
inline void addLoginPass(const char* login, const char* pass) { authLoginPwdList.push_back(std::string(login)+':'+std::string(pass)); };

/**
* Set http Bearer token decode callback function
* @param realm: realm attribute defining scope of resources being accessed
* @param decodeCallback: function callback for decoding base64 token and verify signature,
* tokb64 is the base64 encoded token, secret is the key used for verify token signature, decoded is the token base64 decoded
* without the signature part, returns 0 on success, any other value on fail
* @param secret: key used for checking token authentication
* @param expirationCallback: function callback for retriving token expiration date, MUST be provided, returns numbers of
* seconds since epoch
* @param scopesCheckCallback: function callback for checking any extra field present in token, optionnal and can be set to NULL,
* if provided, tokenDecoded is the token base64 decoded as provided by decodeCallback, resourceUrl is the url of the resource being accessed (some scopes may require different value to get access to specific resources), errDescr will be updated with a description of the error to insert in HTTP header error code insufficient_scope, return true on sucess
*/
inline void setAuthBearerDecodeCallbacks(std::string& realm,
int (*decodeCallback) (const std::string& tokb64, std::string& secret, std::string& decoded),
std::string secret,
time_t (*expirationCallback) (std::string& tokenDecoded),
int (*scopesCheckCallback) (const std::string& tokenDecoded, const std::string& resourceUrl, std::string& errDescr))
{
authBearerRealm = realm;
tokDecodeCallback = decodeCallback;
tokDecodeSecret = secret;
authBearTokDecExpirationCb = expirationCallback;
authBearTokDecScopesCb = scopesCheckCallback;
authBearerEnabled = true;
};

/**
* Set the path to store uploaded files on disk. Used to set the MPFD function.
* @param pathdir: path to a writtable directory
Expand Down
156 changes: 149 additions & 7 deletions src/WebServer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#define KEEPALIVE_MAX_NB_QUERY 25

const char WebServer::authStr[]="Authorization: Basic ";
const char WebServer::authBearerStr[]="Authorization: Bearer ";
const int WebServer::verify_depth=512;
char *WebServer::certpass=NULL;
std::string WebServer::webServerName;
Expand All @@ -68,6 +69,8 @@ time_t HttpSession::sessionLifeTime=20*60;
/*********************************************************************/

WebServer::WebServer(): sslCtx(NULL), s_server_session_id_context(1),
tokDecodeCallback(NULL), authBearTokDecExpirationCb(NULL), authBearTokDecScopesCb(NULL),
authBearerEnabled(false),
httpdAuth(false), exiting(false), exitedThread(0),
nbServerSock(0), disableIpV4(false), disableIpV6(false),
socketTimeoutInSecond(DEFAULT_HTTP_SERVER_SOCKET_TIMEOUT), tcpPort(DEFAULT_HTTP_PORT),
Expand All @@ -83,6 +86,7 @@ WebServer::WebServer(): sslCtx(NULL), s_server_session_id_context(1),

pthread_mutex_init(&peerDnHistory_mutex, NULL);
pthread_mutex_init(&usersAuthHistory_mutex, NULL);
pthread_mutex_init(&tokensAuthHistory_mutex, NULL);
}

/*********************************************************************/
Expand Down Expand Up @@ -198,6 +202,109 @@ bool WebServer::isUserAllowed(const std::string &pwdb64, std::string& login)
return authOK;
}

/**
* Http Bearer token authentication
* @param tokb64: the token string in base64 format
* @param resourceUrl: the token string in base64 format
* @param respHeader: headers to add in HTTP response in case of failed authentication, on tail of WWW-Authenticate attribute
* @return true if token is allowed
*/
bool WebServer::isTokenAllowed(const std::string &tokb64,
const std::string &resourceUrl,
std::string &respHeader)
{
std::string logAuth = "WebServer: Authentication passed for token '"+tokb64+"'";
NvjLogSeverity logAuthLvl = NVJ_DEBUG;
time_t t = time ( NULL );
struct tm * timeinfo;
bool isTokenExpired = true;
bool authOK = false;
time_t expiration = 0;

timeinfo = localtime ( &t );
t = mktime ( timeinfo );

pthread_mutex_lock( &tokensAuthHistory_mutex );
std::map<std::string,time_t>::iterator i = tokensAuthHistory.find (tokb64);

if (i != tokensAuthHistory.end())
{
NVJ_LOG->append(NVJ_DEBUG, "WebServer: token already authenticated");

/* get current timeinfo and compare to the one previously stored */
isTokenExpired = t > i->second;

if (isTokenExpired)
{
/* Remove token from the map to avoid infinite grow of it */
tokensAuthHistory.erase(tokb64);
NVJ_LOG->append(NVJ_DEBUG, "WebServer: removing outdated token from cache '"+tokb64+"'");
}

pthread_mutex_unlock( &tokensAuthHistory_mutex );
return !isTokenExpired;
}

// It's a new token !

std::string tokDecoded;

// Use callback configured to decode token
if (tokDecodeCallback(tokb64, tokDecodeSecret, tokDecoded))
{
logAuth = "WebServer: Authentication failed for token '"+tokb64+"'";
respHeader = "realm=\"" + authBearerRealm;
respHeader += "\",error=\"invalid_token\", error_description=\"invalid signature\"";
goto end;
}

// retrieve expiration date
expiration = authBearTokDecExpirationCb(tokDecoded);

if (!expiration)
{
logAuth = "WebServer: Authentication failed, expiration date not found for token '"+tokb64+"'";
respHeader = "realm=\"" + authBearerRealm;
respHeader += "\",error=\"invalid_token\", error_description=\"no expiration in token\"";
goto end;
}

if (expiration < t)
{
logAuth = "WebServer: Authentication failed, validity expired for token '"+tokb64+"'";
respHeader = "realm=\"" + authBearerRealm;
respHeader += "\",error=\"invalid_token\", error_description=\"token expired\"";
goto end;
}

// check for extra attribute if any callback was set to that purpose
if (authBearTokDecScopesCb)
{
std::string errDescr;

if (authBearTokDecScopesCb(tokDecoded, resourceUrl, errDescr))
{
logAuth = "WebServer: Authentication failed, invalid scope for token '"+tokb64+"'";
respHeader = "realm=\"" + authBearerRealm;
respHeader += "\",error=\"insufficient_scope\",error_description=\"";
respHeader += errDescr +"\"";
goto end;
}
}

// All checks passed successfully, store the token to speed up processing of next request
// proposing same token
authOK = true;
logAuthLvl = NVJ_INFO;
tokensAuthHistory[tokb64] = expiration;

end:
pthread_mutex_unlock( &tokensAuthHistory_mutex );
NVJ_LOG->append(logAuthLvl, logAuth);

return authOK;
}

/***********************************************************************
* recvLine: Receive an ascii line from a socket
* @param c - the socket connected to the client
Expand Down Expand Up @@ -259,7 +366,14 @@ bool WebServer::accept_request(ClientSockData* client, bool authSSL)
bool keepAlive=false;
bool closing=false;
bool isQueryStr=false;

std::string authRespHeader;

if (authBearerEnabled)
{
authOK = false;
authRespHeader = "realm=\"Restricted area: please provide valid token\"";
}

do
{
// Initialisation /////////
Expand Down Expand Up @@ -471,12 +585,26 @@ bool WebServer::accept_request(ClientSockData* client, bool authSSL)
keepAlive = strncmp (httpVers,"1.1", 3) >= 0 ;
}
}

// authorization through bearer token, RFC 6750
if ( strncmp(bufLine+j, authBearerStr, sizeof authBearerStr - 1 ) == 0)
{
j+=sizeof authStr;

std::string tokb64="";
while ( j < (unsigned)bufLineLen && *(bufLine + j) != 0x0d && *(bufLine + j) != 0x0a)
tokb64+=*(bufLine + j++);
if (authBearerEnabled)
authOK=isTokenAllowed(tokb64, urlBuffer, authRespHeader);
continue;
}
}
}

if (!authOK)
{
std::string msg = getHttpHeader( "401 Authorization Required", 0, false);
const char *abh = authRespHeader.empty()? NULL: authRespHeader.c_str();
std::string msg = getHttpHeader( "401 Authorization Required", 0, false, abh);
httpSend(client, (const void*) msg.c_str(), msg.length());
goto FREE_RETURN_TRUE;
}
Expand Down Expand Up @@ -804,7 +932,7 @@ bool WebServer::accept_request(ClientSockData* client, bool authSSL)

if (sizeZip>0 && (client->compression == GZIP))
{
std::string header = getHttpHeader(response.getHttpReturnCodeStr().c_str(), sizeZip, keepAlive, true, &response);
std::string header = getHttpHeader(response.getHttpReturnCodeStr().c_str(), sizeZip, keepAlive, NULL, true, &response);
if ( !httpSend(client, (const void*) header.c_str(), header.length())
|| !httpSend(client, (const void*) gzipWebPage, sizeZip) )
{
Expand All @@ -814,7 +942,7 @@ bool WebServer::accept_request(ClientSockData* client, bool authSSL)
}
else
{
std::string header = getHttpHeader(response.getHttpReturnCodeStr().c_str(), webpageLen, keepAlive, false, &response);
std::string header = getHttpHeader(response.getHttpReturnCodeStr().c_str(), webpageLen, keepAlive, NULL, false, &response);
if ( !httpSend(client, (const void*) header.c_str(), header.length())
|| !httpSend(client, (const void*) webpage, webpageLen) )
{
Expand Down Expand Up @@ -977,7 +1105,12 @@ const char* WebServer::get_mime_type(const char *name)
* \return result of send function (successfull: >=0, otherwise <0)
***********************************************************************/

std::string WebServer::getHttpHeader(const char *messageType, const size_t len, const bool keepAlive, const bool zipped, HttpResponse* response)
std::string WebServer::getHttpHeader(const char *messageType,
const size_t len,
const bool keepAlive,
const char *authBearerAdditionalHeaders,
const bool zipped,
HttpResponse* response)
{
char timeBuf[200];
time_t rawtime;
Expand All @@ -992,8 +1125,17 @@ std::string WebServer::getHttpHeader(const char *messageType, const size_t len,
header+=webServerName+"\r\n";

if (strncmp(messageType, "401", 3) == 0)
header+=std::string("WWW-Authenticate: Basic realm=\"Restricted area: please enter Login/Password\"\r\n");

{
if (authBearerAdditionalHeaders)
{
header+=std::string("WWW-Authenticate: Bearer ");
header+=authBearerAdditionalHeaders;
header+="\r\n";
}
else
header+=std::string("WWW-Authenticate: Basic realm=\"Restricted area: please enter Login/Password\"\r\n");
}

if (response != NULL)
{
if ( response->isCORS() )
Expand Down

0 comments on commit f6dad35

Please sign in to comment.