Example #1
0
/**
 * Fetches the user object from the database and updates the user object in 
 * the cache object, which then has to be re-inserted into the cache.
 * User object is retrieved inside a transaction to avoid replication issues.
 */
static int _oilsAuthReloadUser(jsonObject* cacheObj) {
    int reqid, userId;
    osrfAppSession* session;
	osrfMessage* omsg;
    jsonObject *param, *userObj, *newUserObj = NULL;

    userObj = jsonObjectGetKey( cacheObj, "userobj" );
    userId = oilsFMGetObjectId( userObj );

    session = osrfAppSessionClientInit( "open-ils.cstore" );
    osrfAppSessionConnect(session);

    reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
	omsg = osrfAppSessionRequestRecv(session, reqid, 60);

    if(omsg) {

        osrfMessageFree(omsg);
        param = jsonNewNumberObject(userId);
        reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
	    omsg = osrfAppSessionRequestRecv(session, reqid, 60);
        jsonObjectFree(param);

        if(omsg) {
            newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
            osrfMessageFree(omsg);
            reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
	        omsg = osrfAppSessionRequestRecv(session, reqid, 60);
            osrfMessageFree(omsg);
        }
    }

    osrfAppSessionFree(session); // calls disconnect internally

    if(newUserObj) {

        // ws_ou and wsid are ephemeral and need to be manually propagated
        // oilsFMSetString dupe()'s internally, no need to clone the string
        oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
        oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));

        jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
        jsonObjectSetKey(cacheObj, "userobj", newUserObj);
        return 1;
    } 

    osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
    return 0;
}
Example #2
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;
}
/**
    @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;
}