Discuz 3.X 整合 CAS 的方法
1,新建 CasClient.php
<?php
include_once (dirname ( __FILE__ ) . '/CasClientConfig.php'); // 注意这个
include_once (dirname ( __FILE__ ) . '/CAS.php'); // 注意这个
// 初始化
phpCAS::setDebug ();
// initialize phpCAS
phpCAS::client ( CAS_VERSION_2_0, CAS_SERVER_HOSTNAME, CAS_SERVER_PORT, CAS_SERVER_APP_NAME );
// no SSL validation for the CAS server
phpCAS::setNoCasServerValidation ();
2,新建 CasClientConfig.php
// define ( 'CAS_SERVER_HOSTNAME', 'caslocal.youxituan.com' );
// define ( 'CAS_SERVER_PORT', 80 );
define ( 'CAS_SERVER_HOSTNAME', 'localhost' );
define ( 'CAS_SERVER_PORT', 34382 );
define ( 'CAS_SERVER_APP_NAME', "cas" );
define ( 'LocalLoginPath', '/cas/localogin.php' );
define ( 'LocalCheckPath', '/cas/logincheck.php' );
define ( 'LocalLogOffPath', '/cas/logoff.php' );
3, 修改 class_core.php
error_reporting(E_ALL);
define('IN_DISCUZ', true);
define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -12));
define('DISCUZ_CORE_DEBUG', false);
define('DISCUZ_TABLE_EXTENDABLE', false);
require_once DISCUZ_ROOT."Cas/CasClient.php";
set_exception_handler(array('core', 'handleException'));
if(DISCUZ_CORE_DEBUG) {
3,修改 function_member.php 支持 CAS 登录
增加一个方法
// 新加的方法,用以支持CAS 登录
function userloginCas($username, $password, $email, $uid) {
$return = array ();
$merge = 0;
$status = $uid;
$mockcallback = array($uid, $username, $password, $email, $merge);
$return ['ucresult'] = $mockcallback;
// $return ['ucresult'] ['status'] = 0;
// $return ['ucresult'] ['username'] = $username;
// $return ['ucresult'] ['password'] = $password;
// $return ['ucresult'] ['email'] = $email;
// $return ['ucresult'] ['uid'] = $uid;
// $return ['ucresult'] ['merge'] = $merge;
// }
$tmp = array ();
$duplicate = '';
list ( $tmp ['uid'], $tmp ['username'], $tmp ['password'], $tmp ['email'], $duplicate ) = $return ['ucresult'];
$return ['ucresult'] = $tmp;
if ($duplicate && $return ['ucresult'] ['uid'] > 0 || $return ['ucresult'] ['uid'] <= 0) {
$return ['status'] = 0;
return $return;
}
$member = getuserbyuid ( $return ['ucresult'] ['uid'], 1 );
if (! $member || empty ( $member ['uid'] )) {
$return ['status'] = - 1;
return $return;
}
$return ['member'] = $member;
$return ['status'] = 1;
if ($member ['_inarchive']) {
C::t ( 'common_member_archive' )->move_to_master ( $member ['uid'] );
}
if ($member ['email'] != $return ['ucresult'] ['email']) {
C::t ( 'common_member' )->update ( $return ['ucresult'] ['uid'], array (
'email' => $return ['ucresult'] ['email']
) );
}
return $return;
}
4,修改 class_member.php 添加支持 Cas登录支持
修改 OnLogin 方法
function on_login() {
global $_G;
if ($_G ['uid']) {
$referer = dreferer ();
$ucsynlogin = $this->setting ['allowsynlogin'] ? uc_user_synlogin ( $_G ['uid'] ) : '';
$param = array (
'username' => $_G ['member'] ['username'],
'usergroup' => $_G ['group'] ['grouptitle'],
'uid' => $_G ['member'] ['uid']
);
showmessage ( 'login_succeed', $referer ? $referer : './', $param, array (
'showdialog' => 1,
'locationtime' => true,
'extrajs' => $ucsynlogin
) );
}
list ( $seccodecheck ) = seccheck ( 'login' );
if (! empty ( $_GET ['auth'] )) {
$dauth = authcode ( $_GET ['auth'], 'DECODE', $_G ['config'] ['security'] ['authkey'] );
list ( , , , $secchecklogin2 ) = explode ( "\t", $dauth );
if ($secchecklogin2) {
$seccodecheck = true;
}
}
$seccodestatus = ! empty ( $_GET ['lssubmit'] ) ? false : $seccodecheck;
$invite = getinvite ();
// if(!submitcheck('loginsubmit', 1, $seccodestatus)) {
if (1 == 2) {
$auth = '';
$username = ! empty ( $_G ['cookie'] ['loginuser'] ) ? dhtmlspecialchars ( $_G ['cookie'] ['loginuser'] ) : '';
if (! empty ( $_GET ['auth'] )) {
list ( $username, $password, $questionexist ) = explode ( "\t", authcode ( $_GET ['auth'], 'DECODE', $_G ['config'] ['security'] ['authkey'] ) );
$username = dhtmlspecialchars ( $username );
$auth = dhtmlspecialchars ( $_GET ['auth'] );
}
$cookietimecheck = ! empty ( $_G ['cookie'] ['cookietime'] ) || ! empty ( $_GET ['cookietime'] ) ? 'checked="checked"' : '';
if ($seccodecheck) {
$seccode = random ( 6, 1 ) + $seccode {0} * 1000000;
}
if ($this->extrafile && file_exists ( $this->extrafile )) {
require_once $this->extrafile;
}
$navtitle = lang ( 'core', 'title_login' );
include template ( $this->template );
} else {
if (! empty ( $_GET ['auth'] )) {
list ( $_GET ['username'], $_GET ['password'] ) = daddslashes ( explode ( "\t", authcode ( $_GET ['auth'], 'DECODE', $_G ['config'] ['security'] ['authkey'] ) ) );
}
$loginhash = ! empty ( $_GET ['loginhash'] ) && preg_match ( '/^\w+$/', $_GET ['loginhash'] ) ? $_GET ['loginhash'] : '';
if (! ($_G ['member_loginperm'] = logincheck ( $_GET ['username'] ))) {
captcha::report ( $_G ['clientip'] );
showmessage ( 'login_strike' );
}
if ($_GET ['fastloginfield']) {
$_GET ['loginfield'] = $_GET ['fastloginfield'];
}
$_G ['uid'] = $_G ['member'] ['uid'] = 0;
$_G ['username'] = $_G ['member'] ['username'] = $_G ['member'] ['password'] = '';
phpCAS::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl ();
$auth1 = phpCAS::isAuthenticated ();
$username='';
if ($auth1) {
$username = phpCAS::getUser ();
} else {
phpCAS::forceAuthentication ();
// showmessage ( '尚未登录,<a href="" onclick="return gocas();">前去登录</a>' );
}
$password = '';
$email = phpCAS::getAttribute('Email');
$uid = phpCAS::getAttribute('Uid');
// $result = userlogin($_GET['username'], $_GET['password'], $_GET['questionid'], $_GET['answer'], $this->setting['autoidselect'] ? 'auto' : $_GET['loginfield'], $_G['clientip']);
$result = userloginCas ( $username, $password, $email, $uid );
$uid = $result ['ucresult'] ['uid'];
if (! empty ( $_GET ['lssubmit'] ) && ($result ['ucresult'] ['uid'] == - 3 || $seccodecheck)) {
$_GET ['username'] = $result ['ucresult'] ['username'];
$this->logging_more ( $result ['ucresult'] ['uid'] == - 3 );
}
if ($result ['status'] == - 1) {
if (! $this->setting ['fastactivation']) {
$auth = authcode ( $result ['ucresult'] ['username'] . "\t" . FORMHASH, 'ENCODE' );
showmessage ( 'location_activation', 'member.php?mod=' . $this->setting ['regname'] . '&action=activation&auth=' . rawurlencode ( $auth ) . '&referer=' . rawurlencode ( dreferer () ), array (), array (
'location' => true
) );
} else {
$init_arr = explode ( ',', $this->setting ['initcredits'] );
$groupid = $this->setting ['regverify'] ? 8 : $this->setting ['newusergroupid'];
C::t ( 'common_member' )->insert ( $uid, $result ['ucresult'] ['username'], md5 ( random ( 10 ) ), $result ['ucresult'] ['email'], $_G ['clientip'], $groupid, $init_arr );
$result ['member'] = getuserbyuid ( $uid );
$result ['status'] = 1;
}
}
if ($result ['status'] > 0) {
if ($this->extrafile && file_exists ( $this->extrafile )) {
require_once $this->extrafile;
}
setloginstatus ( $result ['member'], $_GET ['cookietime'] ? 2592000 : 0 );
checkfollowfeed ();
if ($_G ['group'] ['forcelogin']) {
if ($_G ['group'] ['forcelogin'] == 1) {
clearcookies ();
showmessage ( 'location_login_force_qq' );
} elseif ($_G ['group'] ['forcelogin'] == 2 && $_GET ['loginfield'] != 'email') {
clearcookies ();
showmessage ( 'location_login_force_mail' );
}
}
if ($_G ['member'] ['lastip'] && $_G ['member'] ['lastvisit']) {
dsetcookie ( 'lip', $_G ['member'] ['lastip'] . ',' . $_G ['member'] ['lastvisit'] );
}
C::t ( 'common_member_status' )->update ( $_G ['uid'], array (
'lastip' => $_G ['clientip'],
'port' => $_G ['remoteport'],
'lastvisit' => TIMESTAMP,
'lastactivity' => TIMESTAMP
) );
$ucsynlogin = $this->setting ['allowsynlogin'] ? uc_user_synlogin ( $_G ['uid'] ) : '';
$pwold = false;
if ($this->setting ['strongpw'] && ! $this->setting ['pwdsafety']) {
if (in_array ( 1, $this->setting ['strongpw'] ) && ! preg_match ( "/\d+/", $_GET ['password'] )) {
$pwold = true;
}
if (in_array ( 2, $this->setting ['strongpw'] ) && ! preg_match ( "/[a-z]+/", $_GET ['password'] )) {
$pwold = true;
}
if (in_array ( 3, $this->setting ['strongpw'] ) && ! preg_match ( "/[A-Z]+/", $_GET ['password'] )) {
$pwold = true;
}
if (in_array ( 4, $this->setting ['strongpw'] ) && ! preg_match ( "/[^a-zA-z0-9]+/", $_GET ['password'] )) {
$pwold = true;
}
}
if ($_G ['member'] ['adminid'] != 1) {
if ($this->setting ['accountguard'] ['loginoutofdate'] && $_G ['member'] ['lastvisit'] && TIMESTAMP - $_G ['member'] ['lastvisit'] > 90 * 86400) {
C::t ( 'common_member' )->update ( $_G ['uid'], array (
'freeze' => 2
) );
C::t ( 'common_member_validate' )->insert ( array (
'uid' => $_G ['uid'],
'submitdate' => TIMESTAMP,
'moddate' => 0,
'admin' => '',
'submittimes' => 1,
'status' => 0,
'message' => '',
'remark' => ''
), false, true );
manage_addnotify ( 'verifyuser' );
showmessage ( 'location_login_outofdate', 'home.php?mod=spacecp&ac=profile&op=password&resend=1', array (
'type' => 1
), array (
'showdialog' => true,
'striptags' => false,
'locationtime' => true
) );
}
if ($this->setting ['accountguard'] ['loginpwcheck'] && $pwold) {
$freeze = $pwold;
if ($this->setting ['accountguard'] ['loginpwcheck'] == 2 && $freeze) {
C::t ( 'common_member' )->update ( $_G ['uid'], array (
'freeze' => 1
) );
}
}
}
$seccheckrule = & $_G ['setting'] ['seccodedata'] ['rule'] ['login'];
if ($seccheckrule ['allow'] == 2) {
if ($seccheckrule ['nolocal']) {
require_once libfile ( 'function/misc' );
$lastipConvert = process_ipnotice ( convertip ( $_G ['member'] ['lastip'] ) );
$nowipConvert = process_ipnotice ( convertip ( $_G ['clientip'] ) );
if ($lastipConvert != $nowipConvert && stripos ( $lastipConvert, $nowipConvert ) == false && stripos ( $nowipConvert, $lastipConvert ) == false) {
$seccodecheck = true;
}
}
if (! $seccodecheck && $seccheckrule ['pwsimple'] && $pwold) {
$seccodecheck = true;
}
if (! $seccodecheck && $seccheckrule ['outofday'] && $_G ['member'] ['lastvisit'] && TIMESTAMP - $_G ['member'] ['lastvisit'] > $seccheckrule ['outofday'] * 86400) {
$seccodecheck = true;
}
if (! $seccodecheck && $_G ['member_loginperm'] < 4) {
$seccodecheck = true;
}
if (! $seccodecheck && $seccheckrule ['numiptry']) {
$seccodecheck = failedipcheck ( $seccheckrule ['numiptry'], $seccheckrule ['timeiptry'] );
}
if ($seccodecheck && ! $secchecklogin2) {
clearcookies ();
$auth = authcode ( $_GET ['username'] . "\t" . $_GET ['password'] . "\t" . ($_GET ['questionid'] ? 1 : 0) . "\t1", 'ENCODE', $_G ['config'] ['security'] ['authkey'] );
$location = 'member.php?mod=logging&action=login&auth=' . rawurlencode ( $auth ) . '&referer=' . rawurlencode ( dreferer () ) . (! empty ( $_GET ['cookietime'] ) ? '&cookietime=1' : '');
if (defined ( 'IN_MOBILE' )) {
showmessage ( 'login_seccheck2', $location );
} else {
$js = '<script type="text/javascript">location.href=\'' . $location . '\'</script>';
showmessage ( 'login_seccheck2', '', array (
'type' => 1
), array (
'extrajs' => $js
) );
}
}
}
if ($invite ['id']) {
$result = C::t ( 'common_invite' )->count_by_uid_fuid ( $invite ['uid'], $uid );
if (! $result) {
C::t ( 'common_invite' )->update ( $invite ['id'], array (
'fuid' => $uid,
'fusername' => $_G ['username']
) );
updatestat ( 'invite' );
} else {
$invite = array ();
}
}
if ($invite ['uid']) {
require_once libfile ( 'function/friend' );
friend_make ( $invite ['uid'], $invite ['username'], false );
dsetcookie ( 'invite_auth', '' );
if ($invite ['appid']) {
updatestat ( 'appinvite' );
}
}
$param = array (
'username' => $result ['ucresult'] ['username'],
'usergroup' => $_G ['group'] ['grouptitle'],
'uid' => $_G ['member'] ['uid'],
'groupid' => $_G ['groupid'],
'syn' => $ucsynlogin ? 1 : 0
);
$extra = array (
'showdialog' => true,
'locationtime' => true,
'extrajs' => $ucsynlogin
);
if (! $freeze || ! $this->setting ['accountguard'] ['loginpwcheck']) {
$loginmessage = $_G ['groupid'] == 8 ? 'login_succeed_inactive_member' : 'login_succeed';
$location = $invite || $_G ['groupid'] == 8 ? 'home.php?mod=space&do=home' : dreferer ();
} else {
$loginmessage = 'login_succeed_password_change';
$location = 'home.php?mod=spacecp&ac=profile&op=password';
$_GET ['lssubmit'] = 0;
}
if (empty ( $_GET ['handlekey'] ) || ! empty ( $_GET ['lssubmit'] )) {
if (defined ( 'IN_MOBILE' )) {
showmessage ( $loginmessage, $location, $param, array (
'location' => true
) );
} else {
if (! empty ( $_GET ['lssubmit'] )) {
if (! $ucsynlogin) {
$extra ['location'] = true;
}
showmessage ( $loginmessage, $location, $param, $extra );
} else {
$href = str_replace ( "'", "\'", $location );
showmessage ( 'location_login_succeed', $location, array (), array (
'showid' => 'succeedmessage',
'extrajs' => '<script type="text/javascript">' . 'setTimeout("window.location.href =\'' . $href . '\';", 3000);' . '$(\'succeedmessage_href\').href = \'' . $href . '\';' . '$(\'main_message\').style.display = \'none\';' . '$(\'main_succeed\').style.display = \'\';' . '$(\'succeedlocation\').innerHTML = \'' . lang ( 'message', $loginmessage, $param ) . '\';</script>' . $ucsynlogin,
'striptags' => false,
'showdialog' => true
) );
}
}
} else {
showmessage ( $loginmessage, $location, $param, $extra );
}
} else {
$password = preg_replace ( "/^(.{" . round ( strlen ( $_GET ['password'] ) / 4 ) . "})(.+?)(.{" . round ( strlen ( $_GET ['password'] ) / 6 ) . "})$/s", "\\1***\\3", $_GET ['password'] );
$errorlog = dhtmlspecialchars ( TIMESTAMP . "\t" . ($result ['ucresult'] ['username'] ? $result ['ucresult'] ['username'] : $_GET ['username']) . "\t" . $password . "\t" . "Ques #" . intval ( $_GET ['questionid'] ) . "\t" . $_G ['clientip'] );
writelog ( 'illegallog', $errorlog );
loginfailed ( $_GET ['username'] );
failedip ();
$fmsg = $result ['ucresult'] ['uid'] == '-3' ? (empty ( $_GET ['questionid'] ) || $answer == '' ? 'login_question_empty' : 'login_question_invalid') : 'login_invalid';
if ($_G ['member_loginperm'] > 1) {
showmessage ( $fmsg, '', array (
'loginperm' => $_G ['member_loginperm'] - 1
) );
} elseif ($_G ['member_loginperm'] == - 1) {
showmessage ( 'login_password_invalid' );
} else {
showmessage ( 'login_strike' );
}
}
}
}
修改 on_logout 支持同步退出
function on_logout() {
global $_G;
$ucsynlogout = $this->setting ['allowsynlogin'] ? uc_user_synlogout () : '';
if ($_GET ['formhash'] != $_G ['formhash']) {
showmessage ( 'logout_succeed', dreferer (), array (
'formhash' => FORMHASH,
'ucsynlogout' => $ucsynlogout,
'referer' => rawurlencode ( dreferer () )
) );
}
clearcookies ();
$_G ['groupid'] = $_G ['member'] ['groupid'] = 7;
$_G ['uid'] = $_G ['member'] ['uid'] = 0;
$_G ['username'] = $_G ['member'] ['username'] = $_G ['member'] ['password'] = '';
$_G ['setting'] ['styleid'] = $this->setting ['styleid'];
if (defined ( 'IN_MOBILE' )) {
showmessage ( 'location_logout_succeed_mobile', dreferer (), array (
'formhash' => FORMHASH,
'referer' => rawurlencode ( dreferer () )
) );
} else {
// 退出
$service = dreferer () ;
phpCAS::logoutWithRedirectService ( $service );
// 后面这个showmessage提示实际上已经不会执行了
// showmessage ( 'logout_succeed', dreferer (), array (
// 'formhash' => FORMHASH,
// 'ucsynlogout' => $ucsynlogout,
// 'referer' => rawurlencode ( dreferer () )
// ) );
}
}
}
当然,最后把登录改成 到登录界面去登录的方式,这样才会生效,这其实是最简单的方法了。
如下图进行设置
CAS 结构 如下:
其中CAS 包我也做了小小的修改,但这个与主要业务无关,就不传了。
最后,我的版本是 Discuz! X3.1 Release 20131011 。
从 其它系统跳转到要检测是否已经登录,可以作用JS的方法判断CAS Server是否已经登录,如果已经登录,跳转到登录然后再返回
或使得JS方式从后台调用 CAS的方法进行登录,然后使用Ajax的方式,更新界面。
我的CASServer C# 版本 是自己的写的,但我基本上遵循的 CAS 2.0 的协议,所以对于Java版本CAS也差不多。相应代码可参见的GitHub上的代码。
**************************************************** 2014-8-27补充**********************************************************
其实还有一个问题没有说清楚的,
这个CAS Server的模式如下:
这个CAS Server 完全兼容了Discuz的UcCenter,也就是在支持标准CAS 协议的同时,也支持康盛的Uc接口,Discuz完全不再依赖于UC了。
其实在 DZ接入CasServer的时候,首先要关掉UC的同步登录功能。
接下来,由于DZ依赖于UC,要么改造一下CASServer适配为Uccenter,要么,改一下UcCenter来适应CASServer。
我上面的提到的代码中,应该算是一个思路,具体的实现,还是要根据实际情况进行修正。
Discuz 3.X 整合 CAS 的方法的更多相关文章
- 应用整合CAS服务器方法
概要 在开发WEB程序时需要整合CAS实现单点登录,下面介绍一下应用整合CAS服务器的过程. 在开始之前,我们确定CAS服务器已经搭建完毕. 实现步骤 1.新建一个maven项目,引入casclien ...
- dedecmsV5.7和discuz!X3.4整合之后免激活登陆
问题:dedecmsv5.7和discuz!X3.4整合之后,从dede过去的用户,第一次登陆discuz!X3.4,需要激活.后来我就上百度了一番,找到了一个方法 我找到的方法: 1.在dedecm ...
- Spring Security(20)——整合Cas
整合Cas 目录 1.1 配置登录认证 1.1.1 配置AuthenticationEntryPoint 1.1.2 配置CasAuthenticationFilt ...
- Spring Security 3整合CAS 实现SSO
spring security 3整合cas client用于实现各Application之间的单点登录. 1. 需要准备的jar spring-security-core-3.0.8.RELEASE ...
- discuz!X2头像无法显示解决方法
discuz x2刚刚发布,很多站长就迫不及待地将自己的论坛升级. 可是安装完discuz X2之后,就马上发现论坛会的头像都不见了,取而代之的是一个小红叉.会员也没有办法设置自己的头像. 各位站长们 ...
- Spring整合Struts2的方法
一.基本支持 通常我们整合Spring和struts2的目的是让Spring来管理struts2的控制器.也就是说把Action交由Spring来管理,利用IOC的特性把Action注入到业务逻辑中. ...
- Spring整合Hibernate的方法
一.基本支持 Spring 支持大多数流行的 ORM 框架, 包括 Hibernate JDO, TopLink, Ibatis 和 JPA. Spring 对这些 ORM 框架的支持是一致的, 因此 ...
- CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录
1.1 什么是单点登录 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的 ...
- Discuz封锁蜘蛛最有效的方法
闲来无事翻代码,发现一个好东西,Discuz设计者考虑到了有些流氓搜索引擎不遵守roborts.txt,于是设计了一个NOROBOT变量,当这个变量为true 的时候,定义中的搜索引擎都会无法访问,默 ...
随机推荐
- 一、XML语法
xml声明xml指令:<? ?>xml编码与乱码xml元素(标签)CDATA区空格与换行会被认为是标签的内容xml-stylesheet指令解析xml内容 <?xml version ...
- [IE兼容性] Table 之边框 (IE6 IE7 IE8(Q) 中 cellspacing 属性在重合的边框模型的表格中仍然有效)
在 IE6 IE7 IE8(Q) 中,在通过 border-collapse:collapse 使用表格的重合边框模型后,其 cellspacing 属性仍然有效: 在 其他浏览器 中,此时的 cel ...
- linux命令:cat
1:命令介绍: cat用来打印标准输入或连接文件.tac是其相反命令,从最后一行开始打印. 2:命令格式: cat [选项] 文件 3:命令参数: -A, --show-all 等 ...
- 【LeetCode OJ】Evaluate Reverse Polish Notation
Problem link: http://oj.leetcode.com/problems/evaluate-reverse-polish-notation/ According to the wik ...
- XMPP即时通讯
XMPP:XMPP是基于XML的点对点通讯协议,The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议). XMPP可用于服务类实时通讯,表 ...
- OC多线程管理
在OC中多线程管理包含GCD.NSThread.NSOperationQueue. 下面简单介绍. 进程和线程 进程:正在进行中的程序叫做进程,负责程序运行的内存分配. 每一个进程都有自己独立的虚拟内 ...
- HDU 5047
http://acm.hdu.edu.cn/showproblem.php?pid=5047 直到看到题解,我才知道这道题考的是什么 首先交点数是Σ(16*i),区域区分的公式是 边数+点数+1=分成 ...
- EXT遮罩效果
<link href="/resources/ext/resources/css/ext-all.css" rel="stylesheet" type=& ...
- 九、CCAction
之前介绍CCNode的时候说过,动作是指在特定时间内完成移动.缩放.旋转等操作的行为,节点可以通过运行动作来实现动画效果,这里的动作就是指CCAction对象,它有很多的子类,每个子类都封装了不同的动 ...
- Find Minimum in Rotated Sorted Array II
Follow up for "Find Minimum in Rotated Sorted Array":What if duplicates are allowed? Would ...