Class: OpenID::Server::CheckIDRequest

Inherits:
OpenIDRequest show all
Defined in:
lib/openid/server.rb

Overview

A request to confirm the identity of a user.

This class handles requests for openid modes checkid_immediate and checkid_setup .

Instance Attribute Summary collapse

Attributes inherited from OpenIDRequest

#message

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from OpenIDRequest

#namespace

Constructor Details

#initialize(identity, return_to, op_endpoint, trust_root = nil, immediate = false, assoc_handle = nil, claimed_id = nil) ⇒ CheckIDRequest

These parameters are assigned directly as attributes, see the #CheckIDRequest class documentation for their descriptions.

Raises #MalformedReturnURL when the return_to URL is not a URL.

Raises:



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
# File 'lib/openid/server.rb', line 471

def initialize(identity, return_to, op_endpoint, trust_root = nil,
  immediate = false, assoc_handle = nil, claimed_id = nil)
  @assoc_handle = assoc_handle
  @identity = identity
  @claimed_id = (claimed_id or identity)
  @return_to = return_to
  @trust_root = (trust_root or return_to)
  @op_endpoint = op_endpoint
  @message = nil

  if immediate
    @immediate = true
    @mode = "checkid_immediate"
  else
    @immediate = false
    @mode = "checkid_setup"
  end

  if @return_to and
      !TrustRoot::TrustRoot.parse(@return_to)
    raise MalformedReturnURL.new(nil, @return_to)
  end

  return if trust_root_valid

  raise UntrustedReturnURL.new(nil, @return_to, @trust_root)
end

Instance Attribute Details

#assoc_handleObject

Provided in smart mode requests, a handle for a previously established association. nil for dumb mode requests.



439
440
441
# File 'lib/openid/server.rb', line 439

def assoc_handle
  @assoc_handle
end

#claimed_idObject

The claimed identifier. Not present in OpenID 1.x messages.



453
454
455
# File 'lib/openid/server.rb', line 453

def claimed_id
  @claimed_id
end

#identityObject

The OP-local identifier being checked.



449
450
451
# File 'lib/openid/server.rb', line 449

def identity
  @identity
end

#immediateObject

Is this an immediate-mode request?



442
443
444
# File 'lib/openid/server.rb', line 442

def immediate
  @immediate
end

#modeObject

mode

checkid_immediate or checkid_setup



461
462
463
# File 'lib/openid/server.rb', line 461

def mode
  @mode
end

#op_endpointObject

Returns the value of attribute op_endpoint.



463
464
465
# File 'lib/openid/server.rb', line 463

def op_endpoint
  @op_endpoint
end

#return_toObject

The URL to send the user agent back to to reply to this request.



446
447
448
# File 'lib/openid/server.rb', line 446

def return_to
  @return_to
end

#trust_rootObject

This URL identifies the party making the request, and the user will use that to make her decision about what answer she trusts them to have. Referred to as “realm” in OpenID 2.0.



458
459
460
# File 'lib/openid/server.rb', line 458

def trust_root
  @trust_root
end

Class Method Details

.from_message(message, op_endpoint) ⇒ Object

Construct me from an OpenID message.

message

An OpenID checkid_* request Message

op_endpoint

The endpoint URL of the server that this message was sent to.

Raises:

ProtocolError

When not all required parameters are present in the message.

MalformedReturnURL

When the return_to URL is not a URL.

UntrustedReturnURL

When the return_to URL is outside the trust_root.

Raises:



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
541
542
543
544
545
546
547
548
549
550
551
552
553
554
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
# File 'lib/openid/server.rb', line 514

def self.from_message(message, op_endpoint)
  obj = allocate
  obj.message = message
  obj.op_endpoint = op_endpoint
  mode = message.get_arg(OPENID_NS, "mode")
  if mode == "checkid_immediate"
    obj.immediate = true
    obj.mode = "checkid_immediate"
  else
    obj.immediate = false
    obj.mode = "checkid_setup"
  end

  obj.return_to = message.get_arg(OPENID_NS, "return_to")
  if message.is_openid1 and !obj.return_to
    msg = format(
      "Missing required field 'return_to' from %s",
      message,
    )
    raise ProtocolError.new(message, msg)
  end

  obj.identity = message.get_arg(OPENID_NS, "identity")
  obj.claimed_id = message.get_arg(OPENID_NS, "claimed_id")
  if message.is_openid1
    unless obj.identity
      s = "OpenID 1 message did not contain openid.identity"
      raise ProtocolError.new(message, s)
    end
  elsif obj.identity and !obj.claimed_id
    s = ("OpenID 2.0 message contained openid.identity but not " +
           "claimed_id")
    raise ProtocolError.new(message, s)
  elsif obj.claimed_id and !obj.identity
    s = ("OpenID 2.0 message contained openid.claimed_id but not " +
         "identity")
    raise ProtocolError.new(message, s)
  end

  # There's a case for making self.trust_root be a TrustRoot
  # here.  But if TrustRoot isn't currently part of the "public"
  # API, I'm not sure it's worth doing.
  trust_root_param = if message.is_openid1
    "trust_root"
  else
    "realm"
  end
  trust_root = message.get_arg(OPENID_NS, trust_root_param)
  trust_root = obj.return_to if trust_root.nil? || trust_root.empty?
  obj.trust_root = trust_root

  if !message.is_openid1 and !obj.return_to and !obj.trust_root
    raise ProtocolError.new(message, "openid.realm required when " +
                            "openid.return_to absent")
  end

  obj.assoc_handle = message.get_arg(OPENID_NS, "assoc_handle")

  # Using TrustRoot.parse here is a bit misleading, as we're not
  # parsing return_to as a trust root at all.  However, valid
  # URLs are valid trust roots, so we can use this to get an
  # idea if it is a valid URL.  Not all trust roots are valid
  # return_to URLs, however (particularly ones with wildcards),
  # so this is still a little sketchy.
  if obj.return_to and
      !TrustRoot::TrustRoot.parse(obj.return_to)
    raise MalformedReturnURL.new(message, obj.return_to)
  end

  # I first thought that checking to see if the return_to is
  # within the trust_root is premature here, a
  # logic-not-decoding thing.  But it was argued that this is
  # really part of data validation.  A request with an invalid
  # trust_root/return_to is broken regardless of application,
  # right?
  raise UntrustedReturnURL.new(message, obj.return_to, obj.trust_root) unless obj.trust_root_valid

  obj
end

Instance Method Details

#answer(allow, server_url = nil, identity = nil, claimed_id = nil) ⇒ Object

Respond to this request.

allow

Allow this user to claim this identity, and allow the consumer to have this information?

server_url

DEPRECATED. Passing op_endpoint to the #Server constructor makes this optional.

When an OpenID 1.x immediate mode request does not succeed, it gets back a URL where the request may be carried out in a not-so-immediate fashion. Pass my URL in here (the fully qualified address of this server’s endpoint, i.e. http://example.com/server), and I will use it as a base for the URL for a new request.

Optional for requests where #CheckIDRequest.immediate is false or allow is true.

identity

The OP-local identifier to answer with. Only for use when the relying party requested identifier selection.

claimed_id

The claimed identifier to answer with, for use with identifier selection in the case where the claimed identifier and the OP-local identifier differ, i.e. when the claimed_id uses delegation.

If identity is provided but this is not, claimed_id will default to the value of identity. When answering requests that did not ask for identifier selection, the response claimed_id will default to that of the request.

This parameter is new in OpenID 2.0.

Returns an OpenIDResponse object containing a OpenID id_res message.

Raises NoReturnToError if the return_to is missing.

Version 2.0 deprecates server_url and adds claimed_id.

Raises:



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
# File 'lib/openid/server.rb', line 673

def answer(allow, server_url = nil, identity = nil, claimed_id = nil)
  raise NoReturnToError unless @return_to

  unless server_url
    if @message.is_openid2 and !@op_endpoint
      # In other words, that warning I raised in
      # Server.__init__?  You should pay attention to it now.
      raise "#{self} should be constructed with " \
        "op_endpoint to respond to OpenID 2.0 " \
        "messages."
    end

    server_url = @op_endpoint
  end

  mode = if allow
    "id_res"
  elsif @message.is_openid1
    if @immediate
      "id_res"
    else
      "cancel"
    end
  elsif @immediate
    "setup_needed"
  else
    "cancel"
  end

  response = OpenIDResponse.new(self)

  if claimed_id and @message.is_openid1
    raise VersionError, "claimed_id is new in OpenID 2.0 and not " \
      "available for #{@message.get_openid_namespace}"
  end

  claimed_id = identity if identity and !claimed_id

  if allow
    if @identity == IDENTIFIER_SELECT
      unless identity
        raise ArgumentError, "This request uses IdP-driven " \
          "identifier selection.You must supply " \
          "an identifier in the response."
      end

      response_identity = identity
      response_claimed_id = claimed_id

    elsif @identity
      if identity and (@identity != identity)
        raise ArgumentError, "Request was for identity #{@identity}, " \
          "cannot reply with identity #{identity}"
      end

      response_identity = @identity
      response_claimed_id = @claimed_id
    else
      if identity
        raise ArgumentError, "This request specified no identity " \
          "and you supplied #{identity}"
      end
      response_identity = nil
    end

    if @message.is_openid1 and !response_identity
      raise ArgumentError, "Request was an OpenID 1 request, so " \
        "response must include an identifier."
    end

    response.fields.update_args(OPENID_NS, {
      "mode" => mode,
      "op_endpoint" => server_url,
      "return_to" => @return_to,
      "response_nonce" => Nonce.mk_nonce,
    })

    if response_identity
      response.fields.set_arg(OPENID_NS, "identity", response_identity)
      if @message.is_openid2
        response.fields.set_arg(
          OPENID_NS,
          "claimed_id",
          response_claimed_id,
        )
      end
    end
  else
    response.fields.set_arg(OPENID_NS, "mode", mode)
    if @immediate
      if @message.is_openid1 and !server_url
        raise ArgumentError, "setup_url is required for allow=false " \
          "in OpenID 1.x immediate mode."
      end

      # Make a new request just like me, but with
      # immediate=false.
      setup_request = self.class.new(
        @identity,
        @return_to,
        @op_endpoint,
        @trust_root,
        false,
        @assoc_handle,
        @claimed_id,
      )
      setup_request.message = Message.new(@message.get_openid_namespace)
      setup_url = setup_request.encode_to_url(server_url)
      response.fields.set_arg(OPENID_NS, "user_setup_url", setup_url)
    end
  end

  response
end

#cancel_urlObject

Raises:



821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
# File 'lib/openid/server.rb', line 821

def cancel_url
  # Get the URL to cancel this request.
  #
  # Useful for creating a "Cancel" button on a web form so that
  # operation can be carried out directly without another trip
  # through the server.
  #
  # (Except you may want to make another trip through the
  # server so that it knows that the user did make a decision.)
  #
  # Returns a URL as a string.
  raise NoReturnToError unless @return_to

  if @immediate
    raise ArgumentError.new("Cancel is not an appropriate response to " +
                            "immediate mode requests.")
  end

  response = Message.new(@message.get_openid_namespace)
  response.set_arg(OPENID_NS, "mode", "cancel")
  response.to_url(@return_to)
end

#encode_to_url(server_url) ⇒ Object

Raises:



788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# File 'lib/openid/server.rb', line 788

def encode_to_url(server_url)
  # Encode this request as a URL to GET.
  #
  # server_url:: The URL of the OpenID server to make this
  #              request of.
  raise NoReturnToError unless @return_to

  # Imported from the alternate reality where these classes are
  # used in both the client and server code, so Requests are
  # Encodable too.  That's right, code imported from alternate
  # realities all for the love of you, id_res/user_setup_url.
  q = {
    "mode" => @mode,
    "identity" => @identity,
    "claimed_id" => @claimed_id,
    "return_to" => @return_to,
  }

  if @trust_root
    if @message.is_openid1
      q["trust_root"] = @trust_root
    else
      q["realm"] = @trust_root
    end
  end

  q["assoc_handle"] = @assoc_handle if @assoc_handle

  response = Message.new(@message.get_openid_namespace)
  response.update_args(@message.get_openid_namespace, q)
  response.to_url(server_url)
end

#id_selectObject

Is the identifier to be selected by the IDP?



595
596
597
598
# File 'lib/openid/server.rb', line 595

def id_select
  # So IDPs don't have to import the constant
  @identity == IDENTIFIER_SELECT
end

#return_to_verifiedObject

Does the relying party publish the return_to URL for this response under the realm? It is up to the provider to set a policy for what kinds of realms should be allowed. This return_to URL verification reduces vulnerability to data-theft attacks based on open proxies, corss-site-scripting, or open redirectors.

This check should only be performed after making sure that the return_to URL matches the realm.

Raises DiscoveryFailure if the realm URL does not support Yadis discovery (and so does not support the verification process).

Returns true if the realm publishes a document with the return_to URL listed



628
629
630
# File 'lib/openid/server.rb', line 628

def return_to_verified
  TrustRoot.verify_return_to(@trust_root, @return_to)
end

#to_sObject



844
845
846
847
848
849
850
851
852
853
# File 'lib/openid/server.rb', line 844

def to_s
  format(
    "<%s id:%s im:%s tr:%s ah:%s>",
    self.class,
    @identity,
    @immediate,
    @trust_root,
    @assoc_handle,
  )
end

#trust_root_validObject

Is my return_to under my trust_root?

Raises:



601
602
603
604
605
606
607
608
609
610
# File 'lib/openid/server.rb', line 601

def trust_root_valid
  return true unless @trust_root

  tr = TrustRoot::TrustRoot.parse(@trust_root)
  raise MalformedTrustRoot.new(@message, @trust_root) unless tr

  return tr.validate_url(@return_to) if @return_to

  true
end