Class: RSA::SecurID::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/securid.rb,
ext/securid/securid.c

Overview

Manages a single authentication session against the RSA ACE server. Handles the various life cycle events that may occur, such as token resynchronization and pin changes. Includes a test mode that can simulate various responses when an ACE server is not present (for example during local development). Instances of this class should not be reused unless the RSA flow (and this documentation) indicates otherwise (for example, you can issue an #authenticate call after a successful #change_pin call).

This class assumes you have an understanding of the RSA authentication flow. Timeouts on state transitions are enforced by the server, and are not documented here as they may change between ACE releases.

In test mode, this class will send no network traffic and not talk to the RSA agent. The RSA SDK libraries do not even need to be present, just the header files. In the normal mode, the server configuration is imported by the agent directly.

Constant Summary collapse

AUTHENTICATED =
:authenticated
DENIED =
:denied
MUST_CHANGE_PIN =
:must_change_pin
MUST_RESYNCHRONIZE =
:must_resynchronize
UNSTARTED =
nil

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Object

Creates a new session. The session will talk to the RSA server configured via the RSA configuration associated with the installed library, or skip talking to the server entirely if in test mode.

Parameters:

  • options (Hash{Symbol=>Symbol,Boolean}) (defaults to: {})

    A hash of options.

Options Hash (options):

  • :test_mode (Symbol, Boolean) — default: false

    Indicates if the test mode should be enabled, and if so, what mode to enable. Allowed options are: true, false, :resynchronize, :change_pin, :denied. true means that all authentication steps are returned as successful.



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'ext/securid/securid.c', line 308

static VALUE securid_session_initialize(int argc, VALUE *argv, VALUE self)
{
  securid_session_t *session;
  VALUE session_data;
  VALUE options = Qnil;
  VALUE test_mode = Qfalse;

  rb_scan_args(argc, argv, "0:", &options);

  // Allocate a new securid_session_t and wrap it as a ruby object
  session_data = TypedData_Make_Struct(rb_cData, securid_session_t, &securid_session_data_type, session);
  session->handle = SDI_HANDLE_NONE;

  // Stick our new securid_session_t into an instance variable on self 
  rb_ivar_set(self, securid_id_session, session_data);

  // Initalize our status to nil
  rb_ivar_set(self, securid_id_session_status, Qnil);

  // Initalize our test_mode to the supplied option
  if (!NIL_P(options))
  {
    test_mode = rb_hash_aref(options, rb_symTestMode);
  }
  rb_ivar_set(self, securid_id_session_test_mode, test_mode);

  return self;
}

Instance Attribute Details

#statusObject (readonly)

Returns the current state of the session, which is one of AUTHENTICATED, DENIED, MUST_CHANGE_PIN, MUST_RESYNCHRONIZE, or UNSTARTED.



23
24
25
# File 'lib/securid.rb', line 23

def status
  @status
end

Instance Method Details

#authenticate(username, passcode) ⇒ Symbol

Attempts to authenticate the user against the RSA ACE server using the username, pin, and token value. It will indicate if authentication fails due to token issue (pin change or resynchronization request), or a normal authentication failure (denied). The session object will have the returned status stored in its status attribute. A session with a status of AUTHENTICATED or DENIED can not be reused. A session with a status of MUST_CHANGE_PIN or MUST_RESYNCHRONIZE can be used to complete the required step, and then be used to reauthenticate with another call to #authenticate

This method can only be called when the session state is UNSTARTED. Calling it in any other state will result in a RSA::SecurID::SecurIDError being raised.

Parameters:

  • username (String)

    The username to authenticate with.

  • passcode (String)

    The passcode to authenticate with. Passcodes are the user’s pin concatenated with the current token value.

Returns:

Raises:



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'ext/securid/securid.c', line 358

static VALUE securid_session_authenticate(VALUE self, VALUE username, VALUE passcode)
{
  int return_value;
  VALUE session_data;
  VALUE status = Qnil;
  securid_session_t *session;
  SD_CHAR *username_str;
  SD_CHAR *passcode_str;

  // Check that we are in an allowed state
  securid_session_check_status(self, (ID)0);

  if (!securid_session_is_test_mode(self))
  {
    // Fetch our securid_session_t from self
    session_data = rb_ivar_get(self, securid_id_session);
    TypedData_Get_Struct(session_data, securid_session_t, &securid_session_data_type, session);

    // Convert our arguments to C Strings
    username_str = StringValueCStr(username);
    passcode_str = StringValueCStr(passcode);

    // Initalize the session handler
    return_value = SD_Init(&session->handle);
    if (return_value != ACM_OK)
    {
      rb_raise(rb_eSecurIDError, "Failed to initialize session handler - code %d", return_value);
    }

    // Lock the username, part of the Two Step Authentication flow
    return_value = SD_Lock(session->handle, username_str);
    if (return_value != ACM_OK)
    {
      rb_raise(rb_eSecurIDError, "Failed to lock username - code %d", return_value);
    }

    return_value = SD_Check(session->handle, passcode_str, username_str);

    if (return_value == ACM_OK)
    {
      // We are authenticated
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_authenticated);
    } else if (return_value == ACM_ACCESS_DENIED) 
    {
      // We are denied
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_denied);
    } else if (return_value == ACM_NEXT_CODE_REQUIRED)
    {
      // We need the user to resynchronize the token
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_resynchronize);
    } else if (return_value == ACM_NEW_PIN_REQUIRED)
    {
      // We need the user to enter a new pin
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_change_pin);
    } else
    {
      // Internal error of some sort
      rb_raise(rb_eSecurIDError, "Failed to authenticate the user - code %d", return_value);
    }
  } else
  {
    if (securid_session_is_test_mode_resynchronize(self))
    {
      // Force resynchronize in resynchronization test mode
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_resynchronize);
    } else if (securid_session_is_test_mode_change_pin(self))
    {
      // Force pin change in pin change test mode
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_change_pin);
    } else if (securid_session_is_test_mode_denied(self))
    {
      // Force denied in denied test mode
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_denied);
    } else
    {
      // Force success in test mode
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_authenticated);
    }
  }

  // Update our status
  rb_ivar_set(self, securid_id_session_status, status);

  return status;
}

#authenticated?Boolean

Checks if the session is in the AUTHENTICATED state.

Returns:

  • (Boolean)

    true if the session is in the AUTHENTICATED state, false otherwise.



45
46
47
# File 'lib/securid.rb', line 45

def authenticated?
  @status == AUTHENTICATED
end

#cancel_pintrue

Cancels a pin change request. On success the session state will be set back to UNSTARTED. This method can only be called when the session is in the MUST_CHANGE_PIN state. Calling it in any other state will raise a RSA::SecurID::SecurIDError.

Returns:

  • (true)

    Always returns true or raises an error.

Raises:



513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'ext/securid/securid.c', line 513

static VALUE securid_session_cancel_pin(VALUE self)
{
  VALUE session_data;
  securid_session_t *session;
  SD_CHAR *pin_str;
  int return_value;

  // Check that we are in an allowed state
  securid_session_check_status(self, securid_id_session_change_pin);

  if (!securid_session_is_test_mode(self))
  {
    // Fetch our securid_session_t from self
    session_data = rb_ivar_get(self, securid_id_session);
    TypedData_Get_Struct(session_data, securid_session_t, &securid_session_data_type, session);

    return_value = SD_Pin(session->handle, NULL);
    if (return_value != ACM_NEW_PIN_ACCEPTED)
    {
      rb_raise(rb_eSecurIDError, "Failed to cancel changing the pin - code %d", return_value);
    }
  }

  // Update our status to be unstarted.
  rb_ivar_set(self, securid_id_session_status, Qnil);

  return Qtrue;
}

#change_pin(pin) ⇒ true

Changes the pin associated with the token. This method can only be called when the session is in the MUST_CHANGE_PIN state. Calling it in any other state will result in a RSA::SecurID::SecurIDError being raised. After calling #change_pin, the session state is reset to UNSTARTED. A subsequent call to #authenticate is needed to actually authenticate the user.

The typical flow for chaning the pin is first call #authenticate with the old pin, see that we are in the MUST_CHANGE_PIN state, call #change_pin to with the new pin, then call #authenticate with the new pin to finally authorize the user.

Parameters:

  • pin (String)

    The new pin for the token.

Returns:

  • (true)

    Always returns true or raises an error.

Raises:



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'ext/securid/securid.c', line 463

static VALUE securid_session_change_pin(VALUE self, VALUE pin)
{
  VALUE session_data;
  securid_session_t *session;
  SD_CHAR *pin_str;
  int return_value;

  // Check that we are in an allowed state
  securid_session_check_status(self, securid_id_session_change_pin);

  if (!securid_session_is_test_mode(self))
  {
    // Fetch our securid_session_t from self
    session_data = rb_ivar_get(self, securid_id_session);
    TypedData_Get_Struct(session_data, securid_session_t, &securid_session_data_type, session);

    // Convert our arguments to C Strings
    pin_str = StringValueCStr(pin);

    return_value = SD_Pin(session->handle, pin_str);
    if (return_value != ACM_NEW_PIN_ACCEPTED)
    {
      // Changing pin failed for internal reasons
      rb_raise(rb_eSecurIDError, "Failed to change the pin - code %d", return_value);
    }
  } else
  {
    if (securid_session_is_test_mode_change_pin(self))
    {
      // exit pin change test mode for regular test mode
      rb_ivar_set(self, securid_id_session_test_mode, Qtrue);
    }
  }

  // Update our status to be unstarted.
  rb_ivar_set(self, securid_id_session_status, Qnil);

  return Qtrue;
}

#change_pin?Boolean

Checks if the session is in the MUST_CHANGE_PIN state.

Returns:

  • (Boolean)

    true if the session is in the MUST_CHANGE_PIN state, false otherwise.



39
40
41
# File 'lib/securid.rb', line 39

def change_pin?
  @status == MUST_CHANGE_PIN
end

#denied?Boolean

Checks if the session is in the DENIED state.

Returns:

  • (Boolean)

    true if the session is in the DENIED state, false otherwise.



51
52
53
# File 'lib/securid.rb', line 51

def denied?
  @status == DENIED
end

#resynchronize(passcode) ⇒ Symbol

Completes the token resynchronize flow. This method should be called when the server is requesting the user to resynchronize their token. It is not possible to initiate a resychronization request with this method. The session must be in the MUST_RESYNCHRONIZE state. Callig it in any other state will raise a RSA::SecurID::SecurIDError.

Parameters:

  • passcode (String)

    The user’s pin concatenated with the next token value.

Returns:

  • (Symbol)

    Either AUTHENTICATED or DENIED depending on if the resynchronization was successful.

Raises:

  • (SecurIDError)

    if the session is not in the MUST_RESYNCHRONIZE state or the resynchronization fails for any reason other than the passcode not matching.



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'ext/securid/securid.c', line 555

static VALUE securid_session_resynchronize(VALUE self, VALUE passcode)
{
  int return_value;
  VALUE session_data;
  VALUE status;
  securid_session_t *session;
  SD_CHAR *passcode_str;

  // Check that we are in an allowed state
  securid_session_check_status(self, securid_id_session_resynchronize);

  if (!securid_session_is_test_mode(self))
  {
    // Fetch our securid_session_t from self
    session_data = rb_ivar_get(self, securid_id_session);
    TypedData_Get_Struct(session_data, securid_session_t, &securid_session_data_type, session);

    // Convert our arguments to C Strings
    passcode_str = StringValueCStr(passcode);

    // Initalize the session handler
    return_value = SD_Next(session->handle, passcode_str);
    if (return_value == ACM_OK)
    {
      // We are authenticated
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_authenticated);
    } else if (return_value == ACM_ACCESS_DENIED)
    {
      // We are denied 
      status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_denied);
    } else {
      // Internal error of some sort
      rb_raise(rb_eSecurIDError, "Failed to synchronize the token - code %d", return_value);
    }
  } else {
    if (securid_session_is_test_mode_resynchronize(self))
    {
      // exit resynchronization test mode for regular test mode
      rb_ivar_set(self, securid_id_session_test_mode, Qtrue);
    }
    // Force success in test mode
    status = rb_const_get(rb_cRSASecurIDSession, securid_id_session_authenticated);
  }

  // Update our status
  rb_ivar_set(self, securid_id_session_status, status);

  return status;
}

#resynchronize?Boolean

Checks if the session is in the MUST_RESYNCHRONIZE state.

Returns:



33
34
35
# File 'lib/securid.rb', line 33

def resynchronize?
  @status == MUST_RESYNCHRONIZE
end