Esempio n. 1
0
/**
	@brief Run the requested system method.
	@param ctx The method context.
	@return Zero if the method is run successfully; -1 if the method was not run; 1 if the
	method was run and the application code now needs to send a 'request complete' message.

	A system method is a well known method implemented here for all servers.  Instead of
	looking in the shared object, branch on the method name and call the corresponding
	function.
*/
static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
	if( osrfMethodVerifyContext( ctx ) < 0 ) {
		osrfLogError( OSRF_LOG_MARK,  "_osrfAppRunSystemMethod: Received invalid method context" );
		return -1;
	}

	if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
			!strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
		return osrfAppIntrospectAll(ctx);
	}

	if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
			!strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
		return osrfAppIntrospect(ctx);
	}

	if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
			!strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
		return osrfAppEcho(ctx);
	}

	osrfAppRequestRespondException( ctx->session,
			ctx->request, "System method implementation not found");

	return 0;
}
Esempio n. 2
0
/**
	@brief Verify the password received from the client.
	@param ctx The method context.
	@param userObj An object from the database, representing the user.
	@param password An obfuscated password received from the client.
	@return 1 if the password is valid; 0 if it isn't; or -1 upon error.

	(None of the so-called "passwords" used here are in plaintext.  All have been passed
	through at least one layer of hashing to obfuscate them.)

	Take the password from the user object.  Append it to the username seed from memcache,
	as stored previously by a call to the init method.  Take an md5 hash of the result.
	Then compare this hash to the password received from the client.

	In order for the two to match, other than by dumb luck, the client had to construct
	the password it passed in the same way.  That means it neded to know not only the
	original password (either hashed or plaintext), but also the seed.  The latter requirement
	means that the client process needs either to be the same process that called the init
	method or to receive the seed from the process that did so.
*/
static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
		const jsonObject* userObj, const char* uname, const char* password ) {

	// Get the username seed, as stored previously in memcache by the init method
	char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
	if(!seed) {
		return osrfAppRequestRespondException( ctx->session,
			ctx->request, "No authentication seed found. "
			"open-ils.auth.authenticate.init must be called first");
	}

	// Get the hashed password from the user object
	char* realPassword = oilsFMGetString( userObj, "passwd" );

	osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
	osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );

	// Concatenate them and take an MD5 hash of the result
	char* maskedPw = md5sum( "%s%s", seed, realPassword );

	free(realPassword);
	free(seed);

	if( !maskedPw ) {
		// This happens only if md5sum() runs out of memory
		free( maskedPw );
		return -1;  // md5sum() ran out of memory
	}

	osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
			"Testing against provided password %s", maskedPw, password );

	int ret = 0;
	if( !strcmp( maskedPw, password ) )
		ret = 1;

	free(maskedPw);

	return ret;
}
Esempio n. 3
0
/**
	@brief Implement the "complete" method.
	@param ctx The method context.
	@return -1 upon error; zero if successful, and if a STATUS message has been sent to the
	client to indicate completion; a positive integer if successful but no such STATUS
	message has been sent.

	Method parameters:
	- a hash with some combination of the following elements:
		- "username"
		- "barcode"
		- "password" (hashed with the cached seed; not plaintext)
		- "type"
		- "org"
		- "workstation"

	The password is required.  Either a username or a barcode must also be present.

	Return to client: Intermediate authentication seed.

	Validate the password, using the username if available, or the barcode if not.  The
	user must be active, and not barred from logging on.  The barcode, if used for
	authentication, must be active as well.  The workstation, if specified, must be valid.

	Upon deciding whether to allow the logon, return a corresponding event to the client.
*/
int oilsAuthComplete( osrfMethodContext* ctx ) {
	OSRF_METHOD_VERIFY_CONTEXT(ctx);

	const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);

	const char* uname       = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
	const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
	const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
	int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
	const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
	const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));

	const char* ws = (workstation) ? workstation : "";

	if( !type )
		 type = OILS_AUTH_STAFF;

	if( !( (uname || barcode) && password) ) {
		return osrfAppRequestRespondException( ctx->session, ctx->request,
			"username/barcode and password required for method: %s", ctx->method->name );
	}

	oilsEvent* response = NULL;
	jsonObject* userObj = NULL;
	int card_active     = 1;      // boolean; assume active until proven otherwise

	// Fetch a row from the actor.usr table, by username if available,
	// or by barcode if not.
	if(uname) {
		userObj = oilsUtilsFetchUserByUsername( uname );
		if( userObj && JSON_NULL == userObj->type ) {
			jsonObjectFree( userObj );
			userObj = NULL;         // username not found
		}
	}
	else if(barcode) {
		// Read from actor.card by barcode

		osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );

		jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
		jsonObject* card = oilsUtilsQuickReq(
			"open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
		jsonObjectFree( params );

		if( card && card->type != JSON_NULL ) {
			// Determine whether the card is active
			char* card_active_str = oilsFMGetString( card, "active" );
			card_active = oilsUtilsIsDBTrue( card_active_str );
			free( card_active_str );

			// Look up the user who owns the card
			char* userid = oilsFMGetString( card, "usr" );
			jsonObjectFree( card );
			params = jsonParseFmt( "[%s]", userid );
			free( userid );
			userObj = oilsUtilsQuickReq(
					"open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
			jsonObjectFree( params );
			if( userObj && JSON_NULL == userObj->type ) {
				// user not found (shouldn't happen, due to foreign key)
				jsonObjectFree( userObj );
				userObj = NULL;
			}
		}
	}

	if(!userObj) {
		response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
		osrfLogInfo(OSRF_LOG_MARK,  "failed login: username=%s, barcode=%s, workstation=%s",
				uname, (barcode ? barcode : "(none)"), ws );
		osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
		oilsEventFree(response);
		return 0;           // No such user
	}

	// Such a user exists.  Now see if he or she has the right credentials.
	int passOK = -1;
	if(uname)
		passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
	else if (barcode)
		passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );

	if( passOK < 0 ) {
		jsonObjectFree(userObj);
		return passOK;
	}

	// See if the account is active
	char* active = oilsFMGetString(userObj, "active");
	if( !oilsUtilsIsDBTrue(active) ) {
		if( passOK )
			response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
		else
			response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );

		osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
		oilsEventFree(response);
		jsonObjectFree(userObj);
		free(active);
		return 0;
	}
	free(active);

	osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );

	if( !card_active ) {
		osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
		response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
		osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
		oilsEventFree( response );
		jsonObjectFree( userObj );
		return 0;
	}


	// See if the user is even allowed to log in
	if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
		jsonObjectFree(userObj);
		return 0;
	}

	// If a workstation is defined, add the workstation info
	if( workstation != NULL ) {
		osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
		response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
		if(response) {
			jsonObjectFree(userObj);
			osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
			oilsEventFree(response);
			return 0;
		}

	} else {
		// Otherwise, use the home org as the workstation org on the user
		char* orgid = oilsFMGetString(userObj, "home_ou");
		oilsFMSetString(userObj, "ws_ou", orgid);
		free(orgid);
	}

	char* freeable_uname = NULL;
	if(!uname) {
		uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
	}

	if( passOK ) {
		response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );

	} else {
		response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
		osrfLogInfo(OSRF_LOG_MARK,  "failed login: username=%s, barcode=%s, workstation=%s",
				uname, (barcode ? barcode : "(none)"), ws );
	}

	jsonObjectFree(userObj);
	osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
	oilsEventFree(response);

	if(freeable_uname)
		free(freeable_uname);

	return 0;
}
Esempio n. 4
0
int oilsAuthLogin(osrfMethodContext* ctx) {
    OSRF_METHOD_VERIFY_CONTEXT(ctx);

    const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);

    const char* username    = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
    const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
    const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
    const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
    int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
    const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
    const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
    const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));

    const char* ws = (workstation) ? workstation : "";
    if (!type) type = OILS_AUTH_STAFF;

    jsonObject* userObj = NULL; // free me
    oilsEvent* response = NULL; // free me

    /* Use __FILE__, harmless_line_number for creating
     * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
     * giving away information about why an authentication attempt failed.
     */
    int harmless_line_number = __LINE__;

    // translate a generic identifier into a username or barcode if necessary.
    if (identifier && !username && !barcode) {
        if (oilsAuthIdentIsBarcode(identifier, orgloc)) {
            barcode = identifier;
        } else {
            username = identifier;
        }
    }

    if (username) {
        barcode = NULL; // avoid superfluous identifiers
        userObj = oilsUtilsFetchUserByUsername(ctx, username);

    } else if (barcode) {
        userObj = oilsUtilsFetchUserByBarcode(ctx, barcode);

    } else {
        // not enough params
        return osrfAppRequestRespondException(ctx->session, ctx->request,
            "username/barcode and password required for method: %s", 
            ctx->method->name);
    }

    if (!userObj) { // user not found.  
        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
        osrfAppRespondComplete(ctx, oilsEventToJSON(response));
        oilsEventFree(response); // frees event JSON
        return 0;
    }

    long user_id = oilsFMGetObjectId(userObj);

    // username is freed when userObj is freed.
    // From here we can use the username as the generic identifier
    // since it's guaranteed to have a value.
    if (!username) username = oilsFMGetStringConst(userObj, "usrname");

    // See if the user is allowed to login.
    jsonObject* params = jsonNewObject(NULL);
    jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id));
    jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
    jsonObjectSetKey(params, "login_type", jsonNewObject(type));
    if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));

    jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
        ctx,
        "open-ils.auth_internal",
        "open-ils.auth_internal.user.validate", params);
    jsonObjectFree(params);

    if (!authEvt) { // unknown error
        jsonObjectFree(userObj);
        return -1;
    }

    const char* authEvtCode = 
        jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));

    if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
        // Received the generic login failure event.

        osrfLogInfo(OSRF_LOG_MARK,  
            "failed login: username=%s, barcode=%s, workstation=%s",
            username, (barcode ? barcode : "(none)"), ws);

        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
    }

    if (!response && // user exists and is not barred, etc.
        !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) {
        // User provided the wrong password or is blocked from too 
        // many previous login failures.

        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);

        osrfLogInfo(OSRF_LOG_MARK,  
            "failed login: username=%s, barcode=%s, workstation=%s",
                username, (barcode ? barcode : "(none)"), ws );
    }

    // Below here, we know the password check succeeded if no response
    // object is present.

    if (!response && (
        !strcmp(authEvtCode, "PATRON_INACTIVE") ||
        !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
        // Patron and/or card is inactive but the correct password 
        // was provided.  Alert the caller to the inactive-ness.
        response = oilsNewEvent2(
            OSRF_LOG_MARK, authEvtCode,
            jsonObjectGetKey(authEvt, "payload")   // cloned within Event
        );
    }

    if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
        // Validate API returned an unexpected non-success event.
        // To be safe, treat this as a generic login failure.

        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
    }

    if (!response) {
        // password OK and no other events have prevented login completion.

        char* ewhat = "login";

        if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
            response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
            ewhat = "verify";

        } else {
            response = oilsAuthHandleLoginOK(
                ctx, userObj, username, type, orgloc, workstation);
        }

        oilsUtilsTrackUserActivity(
            ctx,
            oilsFMGetObjectId(userObj), 
            ewho, ewhat, 
            osrfAppSessionGetIngress()
        );
    }

    // reply
    osrfAppRespondComplete(ctx, oilsEventToJSON(response));

    // clean up
    oilsEventFree(response);
    jsonObjectFree(userObj);
    jsonObjectFree(authEvt);

	return 0;
}
Esempio n. 5
0
/**
	@brief Implement the "complete" method.
	@param ctx The method context.
	@return -1 upon error; zero if successful, and if a STATUS message has been sent to the
	client to indicate completion; a positive integer if successful but no such STATUS
	message has been sent.

	Method parameters:
	- a hash with some combination of the following elements:
		- "username"
		- "barcode"
		- "password" (hashed with the cached seed; not plaintext)
		- "type"
		- "org"
		- "workstation"
		- "agent" (what software/interface/3rd-party is making the request)
		- "nonce" optional login seed to differentiate logins using the same username.

	The password is required.  Either a username or a barcode must also be present.

	Return to client: Intermediate authentication seed.

	Validate the password, using the username if available, or the barcode if not.  The
	user must be active, and not barred from logging on.  The barcode, if used for
	authentication, must be active as well.  The workstation, if specified, must be valid.

	Upon deciding whether to allow the logon, return a corresponding event to the client.
*/
int oilsAuthComplete( osrfMethodContext* ctx ) {
    OSRF_METHOD_VERIFY_CONTEXT(ctx);

    const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);

    const char* uname       = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
    const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
    const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
    const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
    int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
    const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
    const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
    const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
    const char* nonce       = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));

    const char* ws = (workstation) ? workstation : "";
    if (!nonce) nonce = "";

    // we no longer care how the identifier reaches us, 
    // as long as we have one.
    if (!identifier) {
        if (uname) {
            identifier = uname;
        } else if (barcode) {
            identifier = barcode;
        }
    }

    if (!identifier) {
        return osrfAppRequestRespondException(ctx->session, ctx->request,
            "username/barcode and password required for method: %s", 
            ctx->method->name);
    }

    osrfLogInfo(OSRF_LOG_MARK, 
        "Patron completing authentication with identifer %s", identifier);

    /* Use __FILE__, harmless_line_number for creating
     * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
     * giving away information about why an authentication attempt failed.
     */
    int harmless_line_number = __LINE__;

    if( !type )
         type = OILS_AUTH_STAFF;

    oilsEvent* response = NULL; // free
    jsonObject* userObj = NULL; // free

    char* cache_key = va_list_to_string(
        "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
    jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free

    if (!cacheObj) {
        return osrfAppRequestRespondException(ctx->session,
            ctx->request, "No authentication seed found. "
            "open-ils.auth.authenticate.init must be called first "
            " (check that memcached is running and can be connected to) "
        );
    }

    int user_id = jsonObjectGetNumber(
        jsonObjectGetKeyConst(cacheObj, "user_id"));

    if (user_id == -1) {
        // User was not found during init.  Clean up and exit early.
        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
        osrfAppRespondComplete(ctx, oilsEventToJSON(response));
        oilsEventFree(response); // frees event JSON
        osrfCacheRemove(cache_key);
        jsonObjectFree(cacheObj);
        return 0;
    }

    jsonObject* param = jsonNewNumberObject(user_id); // free
    userObj = oilsUtilsCStoreReqCtx(
        ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
    jsonObjectFree(param);

    char* freeable_uname = NULL;
    if (!uname) {
        uname = freeable_uname = oilsFMGetString(userObj, "usrname");
    }

    // See if the user is allowed to login.

    jsonObject* params = jsonNewObject(NULL);
    jsonObjectSetKey(params, "user_id", 
        jsonNewNumberObject(oilsFMGetObjectId(userObj)));
    jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
    jsonObjectSetKey(params, "login_type", jsonNewObject(type));
    if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));

    jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
        ctx,
        "open-ils.auth_internal",
        "open-ils.auth_internal.user.validate", params);
    jsonObjectFree(params);

    if (!authEvt) {
        // Something went seriously wrong.  Get outta here before 
        // we start segfaulting.
        jsonObjectFree(userObj);
        if(freeable_uname) free(freeable_uname);
        return -1;
    }

    const char* authEvtCode = 
        jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));

    if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
        // Received the generic login failure event.

        osrfLogInfo(OSRF_LOG_MARK,  
            "failed login: username=%s, barcode=%s, workstation=%s",
            uname, (barcode ? barcode : "(none)"), ws);

        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
    }

    int passOK = 0;
    
    if (!response) {
        // User exists and is not barred, etc.  Test the password.

        passOK = oilsAuthVerifyPassword(
            ctx, user_id, identifier, password, nonce);

        if (!passOK) {
            // Password check failed. Return generic login failure.

            response = oilsNewEvent(
                __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);

            osrfLogInfo(OSRF_LOG_MARK,  
                "failed login: username=%s, barcode=%s, workstation=%s",
                    uname, (barcode ? barcode : "(none)"), ws );
        }
    }


    // Below here, we know the password check succeeded if no response
    // object is present.

    if (!response && (
        !strcmp(authEvtCode, "PATRON_INACTIVE") ||
        !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
        // Patron and/or card is inactive but the correct password 
        // was provided.  Alert the caller to the inactive-ness.
        response = oilsNewEvent2(
            OSRF_LOG_MARK, authEvtCode,
            jsonObjectGetKey(authEvt, "payload")   // cloned within Event
        );
    }

    if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
        // Validate API returned an unexpected non-success event.
        // To be safe, treat this as a generic login failure.

        response = oilsNewEvent(
            __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
    }

    if (!response) {
        // password OK and no other events have prevented login completion.

        char* ewhat = "login";

        if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
            response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
            ewhat = "verify";

        } else {
            response = oilsAuthHandleLoginOK(
                ctx, userObj, uname, type, orgloc, workstation);
        }

        oilsUtilsTrackUserActivity(
            ctx,
            oilsFMGetObjectId(userObj), 
            ewho, ewhat, 
            osrfAppSessionGetIngress()
        );
    }

    // reply
    osrfAppRespondComplete(ctx, oilsEventToJSON(response));

    // clean up
    oilsEventFree(response);
    jsonObjectFree(userObj);
    jsonObjectFree(authEvt);
    jsonObjectFree(cacheObj);
    if(freeable_uname)
        free(freeable_uname);

    return 0;
}
Esempio n. 6
0
/**
	@brief Verify the password received from the client.
	@param ctx The method context.
	@param userObj An object from the database, representing the user.
	@param password An obfuscated password received from the client.
	@return 1 if the password is valid; 0 if it isn't; or -1 upon error.

	(None of the so-called "passwords" used here are in plaintext.  All have been passed
	through at least one layer of hashing to obfuscate them.)

	Take the password from the user object.  Append it to the username seed from memcache,
	as stored previously by a call to the init method.  Take an md5 hash of the result.
	Then compare this hash to the password received from the client.

	In order for the two to match, other than by dumb luck, the client had to construct
	the password it passed in the same way.  That means it neded to know not only the
	original password (either hashed or plaintext), but also the seed.  The latter requirement
	means that the client process needs either to be the same process that called the init
	method or to receive the seed from the process that did so.
*/
static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
		const jsonObject* userObj, const char* uname,
		const char* password, const char* nonce ) {

	// Get the username seed, as stored previously in memcache by the init method
	char* seed = osrfCacheGetString( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce );
	if(!seed) {
		return osrfAppRequestRespondException( ctx->session,
			ctx->request, "No authentication seed found. "
			"open-ils.auth.authenticate.init must be called first "
			" (check that memcached is running and can be connected to) "
		);
	}
    
	// We won't be needing the seed again, remove it
	osrfCacheRemove( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce );

	// Get the hashed password from the user object
	char* realPassword = oilsFMGetString( userObj, "passwd" );

	osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
	osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );

	// Concatenate them and take an MD5 hash of the result
	char* maskedPw = md5sum( "%s%s", seed, realPassword );

	free(realPassword);
	free(seed);

	if( !maskedPw ) {
		// This happens only if md5sum() runs out of memory
		free( maskedPw );
		return -1;  // md5sum() ran out of memory
	}

	osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
			"Testing against provided password %s", maskedPw, password );

	int ret = 0;
	if( !strcmp( maskedPw, password ) )
		ret = 1;

	free(maskedPw);

	char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
	jsonObject* countobject = osrfCacheGetObject( countkey );
	if(countobject) {
		long failcount = (long) jsonObjectGetNumber( countobject );
		if(failcount >= _oilsAuthBlockCount) {
			ret = 0;
		    osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, forcing failure state.", uname, failcount);
		}
		if(ret == 0) {
			failcount += 1;
		}
		jsonObjectSetNumber( countobject, failcount );
		osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
		jsonObjectFree(countobject);
	}
	free(countkey);

	return ret;
}
int oilsAuthInternalValidate(osrfMethodContext* ctx) {
    OSRF_METHOD_VERIFY_CONTEXT(ctx);

    const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);

    const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
    const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
    const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
    int org_unit            = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));

    if ( !(user_id && login_type) ) {
        return osrfAppRequestRespondException( ctx->session, ctx->request,
            "Missing parameters for method: %s", ctx->method->name );
    }

    oilsEvent* response = NULL;
    jsonObject *userObj = NULL, *params = NULL;
    char* tmp_str = NULL;
    int user_exists = 0, user_active = 0, 
        user_barred = 0, user_deleted = 0;

    // Confirm user exists, active=true, barred=false, deleted=false
    params = jsonNewNumberStringObject(user_id);
    userObj = oilsUtilsCStoreReqCtx(
        ctx, "open-ils.cstore.direct.actor.user.retrieve", params);
    jsonObjectFree(params);

    if (userObj && userObj->type != JSON_NULL) {
        user_exists = 1;

        tmp_str = oilsFMGetString(userObj, "active");
        user_active = oilsUtilsIsDBTrue(tmp_str);
        free(tmp_str);

        tmp_str = oilsFMGetString(userObj, "barred");
        user_barred = oilsUtilsIsDBTrue(tmp_str);
        free(tmp_str);

        tmp_str = oilsFMGetString(userObj, "deleted");
        user_deleted = oilsUtilsIsDBTrue(tmp_str);
        free(tmp_str);
    }

    if (!user_exists || user_barred || user_deleted) {
        response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
    }

    if (!response && !user_active) {
        // In some cases, it's useful for the caller to know if the
        // patron was unable to login becuase the account is inactive.
        // Return a specific event for this.
        response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
    }

    if (!response && barcode) {
        // Caller provided a barcode.  Ensure it exists and is active.

        int card_ok = 0;
        params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
        jsonObject* card = oilsUtilsCStoreReqCtx(
            ctx, "open-ils.cstore.direct.actor.card.search", params);
        jsonObjectFree(params);

        if (card && card->type != JSON_NULL) {
            tmp_str = oilsFMGetString(card, "active");
            card_ok = oilsUtilsIsDBTrue(tmp_str);
            free(tmp_str);
        }

        jsonObjectFree(card); // card=NULL OK here.

        if (!card_ok) {
            response = oilsNewEvent(
                OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
        }
    }

    // XXX: login permission checks are always global (see 
    // oilsAuthCheckLoginPerm()).  No need to extract the 
    // workstation org unit here.

    if (!response) { // Still OK
        // Confirm user has permission to login w/ the requested type.
        response = oilsAuthCheckLoginPerm(
            ctx, atoi(user_id), org_unit, login_type);
    }


    if (!response) {
        // No tests failed.  Return SUCCESS.
        response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
    }


    jsonObjectFree(userObj); // userObj=NULL OK here.
    osrfAppRespondComplete(ctx, oilsEventToJSON(response));
    oilsEventFree(response);

    return 0;
}
/**
    @brief Implement the session create method
    @param ctx The method context.
    @return -1 upon error; zero if successful, and if a STATUS message has 
    been sent to the client to indicate completion; a positive integer if 
    successful but no such STATUS message has been sent.

    Method parameters:
    - a hash with some combination of the following elements:
        - "user_id"     -- actor.usr (au) ID for the user to cache.
        - "org_unit"    -- actor.org_unit (aou) ID representing the physical 
                           location / context used for timeout, etc. settings.
        - "login_type"  -- login type (opac, staff, temp, persist)
        - "workstation" -- workstation name

*/
int oilsAuthInternalCreateSession(osrfMethodContext* ctx) {
    OSRF_METHOD_VERIFY_CONTEXT(ctx);

    const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);

    const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
    const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
    const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
    int org_unit            = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));

    if ( !(user_id && login_type) ) {
        return osrfAppRequestRespondException( ctx->session, ctx->request,
            "Missing parameters for method: %s", ctx->method->name );
    }

    oilsEvent* response = NULL;

    // fetch the user object
    jsonObject* idParam = jsonNewNumberStringObject(user_id);
    jsonObject* userObj = oilsUtilsCStoreReqCtx(
        ctx, "open-ils.cstore.direct.actor.user.retrieve", idParam);
    jsonObjectFree(idParam);

    if (!userObj) {
        return osrfAppRequestRespondException(ctx->session, 
            ctx->request, "No user found with ID %s", user_id);
    }

    // If a workstation is defined, add the workstation info
    if (workstation) {
        response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);

        if (response) { // invalid workstation.
            jsonObjectFree(userObj);
            osrfAppRespondComplete(ctx, oilsEventToJSON(response));
            oilsEventFree(response);
            return 0;

        } else { // workstation OK.  

            // The worksation org unit supersedes any org unit value 
            // provided via the API.  oilsAuthVerifyWorkstation() sets the 
            // ws_ou value to the WS owning lib.  A value is guaranteed.
            org_unit = atoi(oilsFMGetStringConst(userObj, "ws_ou"));
        }

    } else { // no workstation

        // For backwards compatibility, when no workstation is provided, use 
        // the users's home org as its workstation org unit, regardless of 
        // any API-level org unit value provided.
        const char* orgid = oilsFMGetStringConst(userObj, "home_ou");
        oilsFMSetString(userObj, "ws_ou", orgid);

        // The context org unit defaults to the user's home library when
        // no workstation is used and no API-level value is provided.
        if (org_unit < 1) org_unit = atoi(orgid);
    }

    // determine the auth/cache timeout
    long timeout = oilsAuthGetTimeout(userObj, login_type, org_unit);

    char* string = va_list_to_string("%d.%ld.%ld", 
        (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
    char* authToken = md5sum(string);
    char* authKey = va_list_to_string(
        "%s%s", OILS_AUTH_CACHE_PRFX, authToken);

    oilsFMSetString(userObj, "passwd", "");
    jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
    jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));

    if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
        // Add entries for endtime and reset_interval, so that we can gracefully
        // extend the session a bit if the user is active toward the end of the 
        // timeout originally specified.
        time_t endtime = time( NULL ) + timeout;
        jsonObjectSetKey(cacheObj, "endtime", 
            jsonNewNumberObject( (double) endtime ));

        // Reset interval is hard-coded for now, but if we ever want to make it
        // configurable, this is the place to do it:
        jsonObjectSetKey(cacheObj, "reset_interval",
            jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
    }

    osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
    jsonObjectFree(cacheObj);
    jsonObject* payload = jsonParseFmt(
        "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);

    response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
    free(string); free(authToken); free(authKey);
    jsonObjectFree(payload);

    jsonObjectFree(userObj);
    osrfAppRespondComplete(ctx, oilsEventToJSON(response));
    oilsEventFree(response);

    return 0;
}
Esempio n. 9
0
/**
	@brief Call the function that implements a specified method.
	@param appName Name of the application.
	@param methodName Name of the method.
	@param ses Pointer to the current application session.
	@param reqId The request id of the request invoking the method.
	@param params Pointer to a jsonObject encoding the parameters to the method.
	@return Zero if successful, or -1 upon failure.

	If we can't find a function corresponding to the method, or if we call it and it returns
	a negative return code, send a STATUS message to the client to report an exception.

	A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
*/
int osrfAppRunMethod( const char* appName, const char* methodName,
		osrfAppSession* ses, int reqId, jsonObject* params ) {

	if( !(appName && methodName && ses) ) return -1;

	// Find the application, and then find the method for it
	osrfApplication* app = _osrfAppFindApplication(appName);
	if( !app )
		return osrfAppRequestRespondException( ses,
				reqId, "Application not found: %s", appName );

	osrfMethod* method = osrfAppFindMethod( app, methodName );
	if( !method )
		return osrfAppRequestRespondException( ses, reqId,
				"Method [%s] not found for service %s", methodName, appName );

	#ifdef OSRF_STRICT_PARAMS
	if( method->argc > 0 ) {
		// Make sure that the client has passed at least the minimum number of arguments.
		if(!params || params->type != JSON_ARRAY || params->size < method->argc )
			return osrfAppRequestRespondException( ses, reqId,
				"Not enough params for method %s / service %s", methodName, appName );
	}
	#endif

	// Build an osrfMethodContext, which we will pass by pointer to the function.
	osrfMethodContext context;

	context.session = ses;
	context.method = method;
	context.params = params;
	context.request = reqId;
	context.responses = NULL;

	int retcode = 0;

	if( method->options & OSRF_METHOD_SYSTEM ) {
		retcode = _osrfAppRunSystemMethod(&context);

	} else {

		// Function pointer through which we will call the function dynamically
		int (*meth) (osrfMethodContext*);

		// Open the function that implements the method
		meth = dlsym(app->handle, method->symbol);

		const char* error = dlerror();
		if( error != NULL ) {
			return osrfAppRequestRespondException( ses, reqId,
				"Unable to execute method [%s] for service %s", methodName, appName );
		}

		// Run it
		retcode = meth( &context );
	}

	if(retcode < 0)
		return osrfAppRequestRespondException(
				ses, reqId, "An unknown server error occurred" );

	retcode = _osrfAppPostProcess( &context, retcode );

	if( context.responses )
		jsonObjectFree( context.responses );
	return retcode;
}