]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Sun, 9 May 2010 11:26:40 +0000 (12:26 +0100)
committerTom Hughes <tom@compton.nu>
Sun, 9 May 2010 11:26:40 +0000 (12:26 +0100)
1  2 
app/controllers/user_controller.rb

index 41a08363cc5bb1b3bbca51df6ac49a075afc7ff3,42a50582e9831690dc66a6e21fb50e2c605e11d7..923a6cfec0b93adf3197bebbb0962ac9d0c33b49
@@@ -24,71 -24,13 +24,71 @@@ class UserController < ApplicationContr
      if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
        render :action => 'new'
      else
 -      @user = User.new(params[:user])
 -
 -      @user.status = "pending"
 -      @user.data_public = true
 -      @user.description = "" if @user.description.nil?
 -      @user.creation_ip = request.remote_ip
 -      @user.languages = request.user_preferred_languages
 +      #The redirect from the OpenID provider reenters here again 
 +      #and we need to pass the parameters through to the  
 +      #open_id_authentication function a second time 
 +      if params[:open_id_complete] 
 +        openid_verify('', true) 
 +        #We have set the user.openid_url to nil beforehand. If it hasn't 
 +        #been set to a new valid openid_url, it means the openid couldn't be validated 
 +        if @user.nil? or @user.openid_url.nil? 
 +          render :action => 'new' 
 +          return 
 +        end   
 +      else
 +        @user = User.new(params[:user])
 +
 +        @user.status = "pending"
 +        @user.data_public = true
 +        @user.description = "" if @user.description.nil?
 +        @user.creation_ip = request.remote_ip
 +        @user.languages = request.user_preferred_languages
 +        #Set the openid_url to nil as for one it is used 
 +        #to check if the openid could be validated and secondly 
 +        #to not get dupplicate conflicts for an empty openid  
 +        @user.openid_url = nil
 +
 +        if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0)
 +          if (@user.pass_crypt.nil? or @user.pass_crypt.length == 0)
 +            #if the password is empty, but we have a openid 
 +            #then generate a random passowrd to disable 
 +            #loging in via password 
 +            @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
 +            @user.pass_crypt_confirmation = @user.pass_crypt 
 +          end
 +          #Validate all of the other fields before
 +          #redirecting to the openid provider
 +          if !@user.valid?
 +            render :action => 'new'
 +          else        
 +            #TODO: Is it a problem to store the user variable with respect to password safty in the session variables?
 +            #Store the user variable in the session for it to be accessible when redirecting back from the openid provider
 +            session[:new_usr] = @user
 +            begin
 +              @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +            rescue
 +              flash.now[:error] = t 'user.login.openid invalid'
 +              render :action => 'new'
 +              return
 +            end
 +            #Verify that the openid provided is valid and that the user is the owner of the id
 +            openid_verify(@norm_openid_url, true)
 +            #openid_verify can return in two ways:
 +            #Either it returns with a redirect to the openid provider who then freshly
 +            #redirects back to this url if the openid is valid, or if the openid is not plausible
 +            #and no provider for it could be found it just returns
 +            #we want to just let the redirect through
 +            if response.headers["Location"].nil?
 +              render :action => 'new'
 +            end
 +          end
 +          #At this point there was either an error and the page has been rendered,
 +          #or there is a redirect to the openid provider and the rest of the method
 +          #gets executed whenn this method gets reentered after redirecting back
 +          #from the openid provider
 +          return
 +        end
 +      end
  
        if @user.save
          flash[:notice] = t 'user.new.flash create success message'
      @title = t 'user.account.title'
      @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
  
 +    #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    #open_id_authentication function
 +    if params[:open_id_complete]
 +      openid_verify('', false)
 +      @user.save
 +      return
 +    end
 +
      if params[:user] and params[:user][:display_name] and params[:user][:description]
        @user.display_name = params[:user][:display_name]
        @user.new_email = params[:user][:new_email]
        @user.home_lat = params[:user][:home_lat]
        @user.home_lon = params[:user][:home_lon]
  
 +      @user.openid_url = nil if (params[:user][:openid_url].length == 0)
 +
        if @user.save
          set_locale
  
  
          redirect_to :action => "account", :display_name => @user.display_name
        end
 +
 +      if (params[:user][:openid_url].length > 0)
 +        begin
 +          @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +          if (@norm_openid_url != @user.openid_url)
 +            #If the OpenID has changed, we want to check that it is a valid OpenID and one
 +            #the user has control over before saving the openID as a password equivalent for
 +            #the user.
 +            openid_verify(@norm_openid_url, false)
 +          end
 +        rescue
 +          flash.now[:error] = t 'user.login.openid invalid'
 +        end
 +      end
 +
      else
        if flash[:errors]
          flash[:errors].each do |attr,msg|
      end
    end
  
 +  def openid_specialcase_mapping(openid_url)
 +    #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
 +    #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
 +    #somewhere obscure making it less userfriendly.
 +    if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) )
 +      return 'https://www.google.com/accounts/o8/id'
 +    end
 +
 +    return nil
 +  end  
 +
 +  def openid_verify(openid_url,account_create)
 +    authenticate_with_open_id(openid_url) do |result, identity_url|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +        @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database
 +        @user.openid_url = identity_url
 +      elsif result.missing?
 +        mapped_id = openid_specialcase_mapping(openid_url)
 +        if mapped_id
 +          openid_verify(mapped_id, account_create)
 +        else
 +          flash.now[:error] = t 'user.login.openid missing provider'
 +        end
 +      elsif result.invalid?
 +        flash.now[:error] = t 'user.login.openid invalid'
 +      else
 +        flash.now[:error] = t 'user.login.auth failure'
 +      end
 +    end
 +  end
 +
 +  def open_id_authentication(openid_url)
 +    #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
 +    #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
 +    #don't want to duplicate the do block
 +    #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should
 +    #remember these results, and shouldn't repromt the user for these data each time.
 +    user = nil
 +    authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +        user = User.find_by_openid_url(identity_url)
 +        if user
 +          if user.visible? and user.active?
 +            session[:user] = user.id
 +            session_expires_after 1.month if session[:remember]
 +            return user
 +          else
 +            user = nil
 +            flash.now[:error] = t 'user.login.account not active'
 +          end
 +        else
 +          #We don't have a user registered to this OpenID. Redirect to the create account page
 +          #with username and email filled in if they have been given by the OpenID provider through
 +          #the simple registration protocol
 +          redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url
 +        end
 +      else if result.missing?
 +             #Try and apply some heuristics to make common cases more userfriendly
 +             mapped_id = openid_specialcase_mapping(openid_url)
 +             if mapped_id
 +               open_id_authentication(mapped_id)
 +             else
 +               flash.now[:error] = t 'user.login.openid missing provider'
 +             end
 +           else if result.invalid?
 +                  flash.now[:error] = t 'user.login.openid invalid'
 +                else
 +                  flash.now[:error] = t 'user.login.auth failure'
 +                end
 +           end
 +      end
 +    end
 +    return user
 +  end
 +
    def go_public
      @user.data_public = true
      @user.save
          if params[:user]
            @user.pass_crypt = params[:user][:pass_crypt]
            @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
-           @user.status = "active"
+           @user.status = "active" if @user.status == "pending"
            @user.email_valid = true
  
            if @user.save
      # The user is logged in already, so don't show them the signup
      # page, instead send them to the home page
      redirect_to :controller => 'site', :action => 'index' if session[:user]
 +
 +    @nickname = params['nickname']
 +    @email = params['email']
 +    @openID = params['openid']
 +      
 +    if !params['openid'].nil?
 +        flash.now[:notice] = t 'user.new.openID association'
 +    end
    end
  
    def login
      @title = t 'user.login.title'
  
 -    if params[:user]
 -      email_or_display_name = params[:user][:email]
 -      pass = params[:user][:password]
 -      user = User.authenticate(:username => email_or_display_name, :password => pass)
 -
 -      if user
 -        session[:user] = user.id
 -        session_expires_after 1.month if params[:remember_me]
 -
 -        # The user is logged in, if the referer param exists, redirect
 -        # them to that unless they've also got a block on them, in
 -        # which case redirect them to the block so they can clear it.
 -        if user.blocked_on_view
 -          redirect_to user.blocked_on_view, :referrer => params[:referrer]
 -        elsif params[:referer]
 -          redirect_to params[:referer]
 +    #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    # open_id_authentication function
 +    if params[:open_id_complete]
 +      user = open_id_authentication('')
 +    elsif params[:user]
 +      if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
 +        session[:remember] = params[:remember_me]
 +        #construct the openid request. This will redirect to the OpenID server to ask for validation
 +        #The external OpenID server will then redirect back to the login method and reenters at the top
 +        open_id_authentication(params[:user][:openid_url])
 +        return
 +      else
 +        email_or_display_name = params[:user][:email]
 +        pass = params[:user][:password]
 +
 +        if user = User.authenticate(:username => email_or_display_name, :password => pass)
 +          session[:user] = user.id
 +          session_expires_after 1.month if params[:remember_me]
 +        elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 +          flash.now[:error] = t 'user.login.account not active'
 +        elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
 +          flash.now[:error] = t 'user.login.account suspended'
          else
 -          redirect_to :controller => 'site', :action => 'index'
 +          flash.now[:error] = t 'user.login.auth failure'
          end
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 -        flash.now[:error] = t 'user.login.account not active'
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
 -        flash.now[:error] = t 'user.login.account suspended'
 +      end
 +    end
 +
 +    if user
 +      # The user is logged in, if the referer param exists, redirect
 +      # them to that unless they've also got a block on them, in
 +      # which case redirect them to the block so they can clear it.
 +      if user.blocked_on_view
 +        redirect_to user.blocked_on_view, :referrer => params[:referrer]
 +      elsif params[:referer]
 +        redirect_to params[:referer]
        else
 -        flash.now[:error] = t 'user.login.auth failure'
 +        redirect_to :controller => 'site', :action => 'index'
        end
      end
    end