GBrowse

 view release on metacpan or  search on metacpan

lib/Bio/Graphics/Browser2/UserDB.pm  view on Meta::CPAN

package Bio::Graphics::Browser2::UserDB;

# $Id: UserDB.pm 23607 2010-07-30 17:34:25Z cnvandev $
use strict;
use Bio::Graphics::Browser2;
use Bio::Graphics::Browser2::SendMail;
use CGI qw(:standard);
use DBI;
use Digest::SHA qw(sha1_hex sha1);
use JSON;
use Text::ParseWords 'quotewords';
use Digest::MD5 qw(md5_hex);
use Carp qw(confess cluck croak);

use constant HAVE_OPENID => eval "require Net::OpenID::Consumer; require LWP::UserAgent; 1" || 0;
use constant HAVE_SMTP   => eval "require Net::SMTP;1" || 0;

# SOME CLARIFICATION ON TERMINOLOGY
# "userid"    -- internal dbm ID for a user; a short integer
# "sessionid" -- GBrowse's session ID, a long hexadecimal
# "uploadsid"  -- GBrowse's upload ID, a long hexadecimal

sub new {
  my $class   = shift;
  my $globals = shift;

  my $VERSION = '0.5';
  my $credentials  = $globals->user_account_db 
      || "DBI:mysql:gbrowse_login;user=gbrowse;password=gbrowse";
  
  my $login = DBI->connect($credentials);
  unless ($login) {
      confess "Could not open login database $credentials";
  }

  my $self = bless {
      dbi      => $login,
      globals  => $globals,
      openid   => HAVE_OPENID,
      register => HAVE_SMTP,
  }, ref $class || $class;

  return $self;
}

sub globals  {shift->{globals} };
sub dbi      {shift->{dbi}     };
sub can_openid   {shift->{openid}  };
sub can_register {shift->{register}  };

sub generate_salted_digest {
    my $self     = shift;
    my $password = shift;
    my $salt     = $self->create_key(4);
    return $salt . sha1_hex($salt,$password);
}

sub salted_digest_match {
    my $self   = shift;
    my ($offered,$correct) = @_;
    my ($salt,$digest) = $correct =~ /^(.{4})(.+)/;
    return sha1_hex($salt,$offered) eq $digest;
}

sub is_salted {
    my $self    = shift;
    my $correct = shift;
    return $correct =~ /^[a-zA-Z0-9_]{4}[0-9a-f]{40}$/;
}

sub passwd_match {
    my $self = shift;
    my ($offered,$correct) = @_;
    return $self->is_salted($correct) ? $self->salted_digest_match($offered,$correct)
	                              : sha1($offered) eq $correct;
}

# Get Header - Returns the message found at the top of all confirmation e-mails.
sub get_header {
  my $self = shift;
  my $globals = $self->{globals};
  my $message  = "\nThank you for creating an account with " 
      . $globals->application_name 
      . ": " 
      . $globals->application_name_long . "\n\n";
  $message .= "The account information found below is for your reference only. ";
  $message .= "Please keep all account names and passwords in a safe location ";
  $message .= "and do not share your password with others.";
  return $message;
}

# Get Footer - Returns the message found at the bottom of all e-mails.
sub get_footer {

lib/Bio/Graphics/Browser2/UserDB.pm  view on Meta::CPAN


    my $userdb  = $self->dbi;

    my $session = $self->globals->session($sessionid);
    $session->id eq $sessionid or die "Sessionid unavailable";
    my $uploadsid = $session->uploadsid;

    my $insert_session  = $userdb->prepare(<<END );
REPLACE INTO session (username,sessionid,uploadsid)
     VALUES (?,?,?)
END
    ;
    
    $insert_session->execute($username,$sessionid,$uploadsid)
	or return;
    return $userdb->last_insert_id('','','','');
}

sub set_session_and_uploadsid {
    my $self = shift;
    my ($userid,$sessionid,$uploadsid) = @_;

    my $userdb = $self->dbi;
    $userdb->do('UPDATE session SET sessionid=?,uploadsid=? WHERE userid=?',
		undef,
		$sessionid,$uploadsid,$userid) or die $userdb->errstr;
}

sub delete_user_by_username {
    my $self = shift;
    my $username = shift;
    my $userdb = $self->dbi;
    my $userid = $self->userid_from_username($username) or return;
    local $userdb->{AutoCommit} = 0;
    local $userdb->{RaiseError} = 1;
    eval {
	$userdb->do('DELETE FROM users        WHERE userid=?',undef,$userid);
	$userdb->do('DELETE FROM session      WHERE userid=?',undef,$userid);
	$userdb->do('DELETE FROM openid_users WHERE userid=?',undef,$userid);
	$userdb->do('DELETE FROM uploads      WHERE userid=?',undef,$userid);
	$userdb->do('DELETE FROM sharing      WHERE userid=?',undef,$userid);
	$userdb->commit();
    };
    if ($@) {
	warn "Account deletion failed due to $@. Rolling back.";
	eval {$userdb->rollback()};
    }
    1;
}

#####################################
# BUG!
# Everything below here supports the
# login.js script, which expects return
# values as various combinations of
# strings and JSON structures.
# This means API is strongly tied
# to database queries.
#####################################

# Validate - Ensures that a non-openid user's credentials are correct.
sub do_validate {
  my $self = shift;
  my ($user,$pass,$remember) = @_;
  
  my $userdb = $self->{dbi};
  my $update;

  # remove dangling unconfirmed accounts here
  $self->check_old_confirmations();

#  return $self->string_result('Usernames cannot contain any backslashes, whitespace or non-ascii characters.')
  return $self->code_result('INVALID_NAME'=>'Usernames cannot contain any backslashes, whitespace or non-ascii characters.')
      unless $self->check_user($user);

  my $userid = $self->userid_from_username($user);
  my $nowfun = $self->nowfun();

  # WARNING: bad design here
  # EXPLANATION: a remember value of "2" means to update last_login
  # but not to retrieve session ID. This seems to be requested during account
  # editing/updating.
  if($remember == 2) {
      $update = $userdb->prepare(
	  "UPDATE users SET last_login=$nowfun WHERE userid=? AND confirmed=1");
  } else {
      $update = $userdb->prepare(
	  "UPDATE users SET last_login=$nowfun,remember=$remember WHERE userid=? AND confirmed=1");
  }

  my $select = $userdb->prepare(
      "SELECT sessionid,email,confirmed,pass FROM users as a,session as b WHERE a.userid=b.userid and a.userid=?");
  $select->execute($userid)
      or return $self->dbi_err;

  # BUG: this is truly nasty -- the session id is found by string searching in login.js!!!!
  my ($session,$email,$confirmed,$correct_pass) = $select->fetchrow_array;

  if ($session && $self->passwd_match($pass,$correct_pass)) {
      if ($confirmed) {
	  my $result = $remember == 2 ? 'Success' : "session".$session;
	  return $self->string_result($result);
      } else {
	  return $self->string_result("unconfirmed${email}");
      }
  } else {
      return $self->string_result('Invalid username or password provided, please try again.'); 
  }

  # update time login now
  $update->execute($userid)
      or return $self->dbi_err;
}

# Add User Check - Checks to see if the user has already been added.
sub do_add_user_check {
  my $self = shift;
  my ($user,$email,$fullname,$pass,$userid) = @_;
  
  my $userdb = $self->dbi;
  

lib/Bio/Graphics/Browser2/UserDB.pm  view on Meta::CPAN

	$old = $pass;
    }

    my $querystring  = "UPDATE users       ";
    $querystring .= "   SET $column  = ?";
    $querystring .= " WHERE userid   = ?";

    my $update = $userdb->prepare($querystring);
    unless($update->execute($new,$userid)) {
	if ($column eq 'email') {
	    return $self->string_result("New e-mail already in use, please try another.");
	} else {
	    return $self->dbi_err;
	}
    }

    if (DBI->errstr =~ m/for key 3$/) {
	return $self->string_result("New e-mail already in use, please try another.");
    }

    my $rows = $update->rows;
    if($rows == 1) {
	return $self->string_result("Success");
    } elsif ($rows == 0) {
	my $explanation = $column eq 'pass'  ? 'password' 
	                 :$column eq 'email' ? 'email address'
			 :'information';
	return $self->string_result("Incorrect $explanation provided, please check your spelling.");
    } else {
	if(($column eq 'email') and ($rows == -1)) {
	    return $self->string_result("New e-mail already in use, please try another.");
	} else {
	    return $self->string_result("Error: $rows rows returned, please consult your service host.");
	}
    }
    return $self->programmer_error;
}
  
# E-mail Info - Sends an e-mail when a user has forgotten their password.
sub do_email_info {
  my $self = shift;
  my $email = shift;
  my $globals = $self->{globals};
  my $userdb = $self->{dbi};
  
  return $self->string_result("Invalid e-mail address provided.")
      unless $self->check_email($email);

  my ($user,$rows,$openid_ref) = $self->do_retrieve_user($email);
  my @openids = @$openid_ref;
  my $openid  = "";
  
  return $self->string_result($user) unless $rows == 1;

  if (@openids) {foreach(@openids) {$openid .= "$_\n             ";}}
  else {$openid = "None\n";}

  my $pass = $self->create_key('8');
  my $message  = "\nYour password has been reset to the one seen below. To fix this,";
     $message .= " select \"My Account\" from the log in menu and log in with the";
     $message .= " credentials found below.\n\n    Username: $user\n    ";
     $message .= "Password: $pass\n\n    OpenIDs: $openid\n\n";
     $message .= $self->get_footer();

  my ($status,$err) = $self->Bio::Graphics::Browser2::SendMail::do_sendmail({
			     from       => $globals->email_address,
			     from_title => $globals->application_name,
			     to         => $email,
			     subject    => $globals->application_name . " Account Information",
			     msg        => $message
			    },$globals);
  return $self->string_result($err) unless $status;

  my $secret = $self->generate_salted_digest($pass);
  my $update = $userdb->prepare(
    "UPDATE users SET pass=? WHERE userid=? AND email=? AND confirmed=1");
  my $userid = $self->userid_from_username($user);
  $update->execute($secret,$userid,$email)
    or return $self->dbi_err;

  return $self->string_result('Success');
}

sub set_password {
    my $self = shift;
    my ($userid,$password) = @_;
    my $userdb   = $self->dbi;
    my $secret = $self->generate_salted_digest($password);
    my $update = $userdb->prepare(
	"UPDATE users SET pass=? WHERE userid=?") or die $userdb->errstr;
    my $status = $update->execute($secret,$userid) or die $userdb->errstr;
    return $status;
}

# Retrieve User - Gets the username associated with a given e-mail.
sub do_retrieve_user {
  my $self = shift;
  my $email = shift;
  
  my $userdb = $self->{dbi};
  
  my @openids;

  my $users = $userdb->selectcol_arrayref(
    "SELECT username FROM users as a,session as b WHERE a.userid=b.userid AND email=? AND confirmed=1",
  	undef,
  	$email)
  or return $self->dbi_err;

  my $rows = @$users;
  if ($rows == 1) {
    my $user  = $users->[0];
    my $query = $userdb->prepare(
      "SELECT openid_url FROM openid_users,session WHERE openid_users.userid=session.userid and session.username=?");
    $query->execute($user)
      or return $self->dbi_err;

    while (my $openid = $query->fetchrow_array) {
      push (@openids,$openid);
    }

lib/Bio/Graphics/Browser2/UserDB.pm  view on Meta::CPAN

  my $userdb = $self->dbi;
  my $userid = $self->userid_from_username($user);
  return $self->string_result("Error: unknown user $user") unless $userid;

  my $sessionid = $self->get_sessionid($userid);
  $userdb->do('DELETE FROM session where userid=?',undef,$userid);
  my $session = $self->globals->session($sessionid);
  $session->delete;
  $session->flush;

  $userdb->do('DELETE FROM users WHERE userid=?',undef,$userid);

  my $query = $userdb->prepare(
    "DELETE FROM openid_users WHERE userid=?");
  if ($query->execute($userid)) {
      return $self->string_result('Success');
  } else {
      return $self->dbi_err;
  }
  return;
}

######################## O P E N I D   F U N C T I O N S #########################
# Check OpenID - Sends a user to their openid host for confirmation.

# BUG: ALL THE OPENID FUNCTIONS NEED TO BE REVISED
sub do_check_openid {
    my $self = shift;
    my $globals = $self->{globals};
    my ($openid, $sessionid, $source, $option) = @_;
    
    my $return_to  = $globals->gbrowse_url($source)."/?openid_confirm=1;page=$option;s=$sessionid";

    my $csr = Net::OpenID::Consumer->new(
        ua              => LWP::UserAgent->new,
        args            => CGI->new,
        consumer_secret => Bio::Graphics::Browser2->openid_secret,
        required_root   => "http://$ENV{'HTTP_HOST'}/"
    );

    my $claimed_identity = $csr->claimed_identity($openid)
        or return $self->string_result("The URL provided is not a valid OpenID, please check your spelling and try again.");
    
    my $check_url = $claimed_identity->check_url(
        return_to  => $return_to,
        trust_root => "http://$ENV{'HTTP_HOST'}/",
        delayed_return => 1
    );
    # request information about email address and full name
    $check_url .= "&openid.ns.ax=http://openid.net/srv/ax/1.0&openid.ax.mode=fetch_request&openid.ax.required=email,firstname,lastname&openid.ax.type.email=http://axschema.org/contact/email&openid.ax.type.firstname=http://axschema.org/namePerson/firs...

    # this shouldn't work, but oddly it does.
    # it has something to do with prototype ajax and the Location: string
    return (200,'text/html',"Location: $check_url"); # shouldn't work?

    # this should work, but oddly it doesn't
    # return (302,undef,$check_url);

}

# Confirm OpenID - Checks that the returned credentials are valid.
sub do_confirm_openid {
    my $self = shift;
    my ($callbacks, $sessionid, $option,$email,$fullname) = @_;
    
    my $userdb = $self->{dbi};
    
    my ($error, @results, $select, $user, $only);

    my $csr = Net::OpenID::Consumer->new(
        ua              => LWP::UserAgent->new,
        args            => $callbacks,
        consumer_secret => Bio::Graphics::Browser2->openid_secret,
        required_root   => "http://$ENV{'HTTP_HOST'}/"
    );

    if ($option eq "openid-add") {
        ($user, $only) = $userdb->selectrow_array(
	    "SELECT b.username,a.openid_only FROM users as a,session as b WHERE b.sessionid=? AND a.userid=b.userid",
	    undef,
	    $sessionid)
	    or return(200,'application/json',[{error=>'Error: '.$userdb->errstr.'.'}]);
        unless (defined $user) {
            push @results,{error=>"Error: Wrong session ID provided, please try again."};
            return (200,'application/json',\@results);
        }
    }

    $csr->handle_server_response(
        not_openid => sub {
            push @results,{user=>$user,only=>$only,error=>"Invalid OpenID provided, please check your spelling."};
        },
        setup_required => sub {
            push @results,{user=>$user,only=>$only,error=>"Error: Your OpenID requires setup."};
        },
        cancelled => sub {
            push @results,{user=>$user,only=>$only,error=>"OpenID verification cancelled."};
        },
        verified => sub {
            my $vident = shift;
            if($option eq "openid-add") {
		push @results,$self->do_add_openid_to_account($sessionid, $user, $vident->url, $only)
            } else {
		push @results,$self->do_get_openid($vident->url,$email,$fullname);
            }
        },
        error => sub {
            $error = $csr->err;
            push @results,{user=>$user,only=>$only,error=>"Error validating identity: $error."};
        }
    );
    return (200,'application/json',\@results);
}

sub do_get_gecos {
    my $self = shift;
    my $user = shift;
    my $sessionid = $self->sessionid_from_username($user) or return '';
    my $fullname = $self->fullname_from_sessionid($sessionid);
    return $self->string_result($fullname);
}



( run in 0.564 second using v1.01-cache-2.11-cpan-ceb78f64989 )