├── .env.example ├── .gitattributes ├── .gitignore ├── LICENSE ├── app ├── Console │ ├── Commands │ │ └── MakeAdmin.php │ └── Kernel.php ├── Exceptions │ ├── BindOauthFailedException.php │ ├── Handler.php │ ├── InvalidArgumentException.php │ └── UserException.php ├── Http │ ├── Controllers │ │ ├── Admin │ │ │ ├── HomeController.php │ │ │ ├── ServiceController.php │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── OAuthController.php │ │ │ └── RegisterController.php │ │ ├── Controller.php │ │ ├── HomeController.php │ │ └── PasswordController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Admin.php │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── Request.php │ └── routes.php ├── Interactions │ └── UserLogin.php ├── Models │ └── UserOauth.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Repositories │ ├── ServiceRepository.php │ └── UserRepository.php ├── Services │ └── TickerLocker.php ├── Traits │ ├── Response │ │ └── ShowMessage.php │ └── ValidateInput.php ├── User.php └── helpers.php ├── artisan ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cas.php ├── cas_server.php ├── compile.php ├── database.php ├── filesystems.php ├── mail.php ├── queue.php ├── services.php ├── session.php ├── trustedproxy.php └── view.php ├── database ├── .gitignore ├── factories │ └── ModelFactory.php ├── migrations │ ├── .gitkeep │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2016_08_01_061914_create_services_table.php │ ├── 2016_08_01_061918_create_tickets_table.php │ ├── 2016_08_01_061923_create_service_hosts_table.php │ ├── 2016_09_20_124536_create_oauth_table.php │ ├── 2016_09_27_232302_add_profile_to_user_oauth_table.php │ ├── 2016_10_25_083524_create_proxy_granting_tickets_table.php │ ├── 2016_10_25_083620_add_proxies_to_tickets_table.php │ ├── 2016_10_25_092220_add_allow_proxy_to_services_table.php │ └── 2016_10_29_083634_change_ticket_column_length.php └── seeds │ ├── .gitkeep │ └── DatabaseSeeder.php ├── gulpfile.js ├── pack.sh ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt └── web.config ├── readme.md ├── readme_zh.md ├── resources ├── assets │ ├── js │ │ ├── admin │ │ │ ├── admin.js │ │ │ ├── service │ │ │ │ └── index.js │ │ │ └── user │ │ │ │ └── index.js │ │ ├── common │ │ │ ├── backend-router-generator.js │ │ │ ├── common.js │ │ │ ├── errors.js │ │ │ ├── jquery-ajax.js │ │ │ └── translator.js │ │ └── front │ │ │ └── home.js │ ├── less │ │ ├── mixins.less │ │ ├── sb-admin-2.less │ │ └── variables.less │ └── sass │ │ └── app.scss ├── lang │ ├── cn │ │ ├── admin.php │ │ ├── auth.php │ │ ├── common.php │ │ ├── message.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php │ └── en │ │ ├── admin.php │ │ ├── auth.php │ │ ├── common.php │ │ ├── message.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── admin │ ├── dashboard.blade.php │ ├── service.blade.php │ └── user.blade.php │ ├── auth │ ├── emails │ │ └── password.blade.php │ ├── logged_out.blade.php │ ├── login.blade.php │ ├── login_warn.blade.php │ ├── passwords │ │ ├── email.blade.php │ │ └── reset.blade.php │ └── register.blade.php │ ├── common │ └── message.blade.php │ ├── errors │ └── 503.blade.php │ ├── home.blade.php │ ├── layouts │ ├── admin.blade.php │ └── app.blade.php │ └── vendor │ ├── .gitkeep │ └── bootbox.blade.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests └── TestCase.php └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_KEY= 3 | APP_DEBUG=true 4 | APP_LOG_LEVEL=debug 5 | APP_URL=http://localhost 6 | APP_LOCALE=en 7 | 8 | DB_CONNECTION=mysql 9 | DB_HOST=127.0.0.1 10 | DB_PORT=3306 11 | DB_DATABASE=homestead 12 | DB_USERNAME=homestead 13 | DB_PASSWORD=secret 14 | 15 | CACHE_DRIVER=file 16 | SESSION_DRIVER=file 17 | QUEUE_DRIVER=sync 18 | 19 | REDIS_HOST=127.0.0.1 20 | REDIS_PASSWORD=null 21 | REDIS_PORT=6379 22 | 23 | MAIL_DRIVER=smtp 24 | MAIL_HOST=mailtrap.io 25 | MAIL_PORT=2525 26 | MAIL_USERNAME=null 27 | MAIL_PASSWORD=null 28 | MAIL_ENCRYPTION=null 29 | 30 | CAS_LOCK_TIMEOUT=5000 31 | CAS_TICKET_EXPIRE=300 32 | CAS_TICKET_LEN=32 33 | CAS_PROXY_GRANTING_TICKET_EXPIRE=7200 34 | CAS_PROXY_GRANTING_TICKET_LEN=64 35 | CAS_PROXY_GRANTING_TICKET_IOU_LEN=64 36 | CAS_VERIFY_SSL=true 37 | CAS_SERVER_ALLOW_RESET_PWD=true 38 | CAS_SERVER_ALLOW_REGISTER=true 39 | CAS_SERVER_DISABLE_PASSWORD_LOGIN=false 40 | CAS_SERVER_NAME="Central Authentication Service" 41 | 42 | TRUSTED_PROXIES=127.0.0.1 43 | TRUSTED_HEADER_CLIENT_IP= 44 | TRUSTED_HEADER_CLIENT_HOST= 45 | TRUSTED_HEADER_CLIENT_PROTO= 46 | TRUSTED_HEADER_CLIENT_PORT= 47 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /node_modules 3 | /public/storage 4 | /public/build 5 | /public/js 6 | /public/css 7 | Homestead.yaml 8 | Homestead.json 9 | .env 10 | .idea 11 | .phpstorm.meta.php 12 | _ide_helper.php 13 | _ide_helper_models.php 14 | build.zip 15 | composer.lock 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 leo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/Console/Commands/MakeAdmin.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 38 | } 39 | 40 | /** 41 | * Execute the console command. 42 | * 43 | * @return mixed 44 | */ 45 | public function handle() 46 | { 47 | $name = 'admin'; 48 | $email = 'admin@admin.com'; 49 | $password = $this->option('password') ?: Str::random(12); 50 | $this->userRepository->create( 51 | [ 52 | 'name' => $name, 53 | 'real_name' => 'admin', 54 | 'email' => $email, 55 | 'password' => $password, 56 | 'admin' => true, 57 | 'enabled' => true, 58 | ] 59 | ); 60 | $this->info(sprintf('name: %s, password: %s, email: %s', $name, $password, $email)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 29 | // ->hourly(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Exceptions/BindOauthFailedException.php: -------------------------------------------------------------------------------- 1 | wantsJson()) { 56 | return response()->json(['error' => $e->getMessage()], 422); 57 | } else { 58 | //todo render error page 59 | } 60 | } 61 | 62 | if ($e instanceof ValidationException && !$e->getResponse()) { 63 | $e->response = $this->buildFailedValidationResponse($request, $this->formatValidationErrors($e->validator)); 64 | } 65 | 66 | return parent::render($request, $e); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/Exceptions/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 34 | $this->serviceRepository = $serviceRepository; 35 | } 36 | 37 | public function indexAction() 38 | { 39 | return view( 40 | 'admin.dashboard', 41 | [ 42 | 'user' => $this->userRepository->dashboard(), 43 | 'service' => $this->serviceRepository->dashboard(), 44 | ] 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/ServiceController.php: -------------------------------------------------------------------------------- 1 | serviceRepository = $serviceRepository; 30 | } 31 | 32 | public function index(Request $request) 33 | { 34 | $page = $request->get('page', 1); 35 | $limit = 20; 36 | $search = $request->get('search', ''); 37 | $services = $this->serviceRepository->getList($search, $page, $limit); 38 | 39 | return view( 40 | 'admin.service', 41 | [ 42 | 'services' => $services, 43 | 'query' => [ 44 | 'search' => $search, 45 | ], 46 | ] 47 | ); 48 | } 49 | 50 | public function store(Request $request) 51 | { 52 | $all = $request->all(); 53 | $all['hosts'] = $this->formatHosts($request); 54 | $service = $this->serviceRepository->create($all); 55 | $service->load('hosts'); 56 | 57 | return response()->json(['msg' => trans('admin.service.add_ok')]); 58 | } 59 | 60 | public function update(Service $service, Request $request) 61 | { 62 | $all = $request->all(); 63 | $all['hosts'] = $this->formatHosts($request); 64 | $this->serviceRepository->update($all, $service); 65 | 66 | return response()->json(['msg' => trans('admin.service.edit_ok')]); 67 | } 68 | 69 | protected function formatHosts(Request $request) 70 | { 71 | $hosts = explode("\n", $request->get('hosts', '')); 72 | $result = []; 73 | foreach ($hosts as $host) { 74 | $host = trim($host); 75 | if (empty($host)) { 76 | continue; 77 | } 78 | 79 | if (filter_var($host, FILTER_VALIDATE_URL)) { 80 | $realHost = parse_url($host, PHP_URL_HOST); 81 | $result[] = $realHost; 82 | } else { 83 | $result[] = $host; 84 | } 85 | } 86 | 87 | return $result; 88 | } 89 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/UserController.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 30 | } 31 | 32 | public function index(Request $request) 33 | { 34 | $page = $request->get('page', 1); 35 | $limit = 20; 36 | $search = $request->get('search', ''); 37 | $enabled = $request->get('enabled', null); 38 | if ($enabled === '') { 39 | $enabled = null; 40 | } 41 | $users = $this->userRepository->getList($search, $enabled, null, $page, $limit); 42 | foreach ($users as $user) { 43 | $user->load('oauth'); 44 | } 45 | 46 | return view( 47 | 'admin.user', 48 | [ 49 | 'users' => $users, 50 | 'query' => [ 51 | 'search' => $search, 52 | 'enabled' => is_null($enabled) ? '' : $enabled, 53 | ], 54 | ] 55 | ); 56 | } 57 | 58 | public function store(Request $request) 59 | { 60 | $user = $this->userRepository->create($request->all()); 61 | 62 | return response()->json(['msg' => trans('admin.user.add_ok')]); 63 | } 64 | 65 | public function update(User $user, Request $request) 66 | { 67 | $user = $this->userRepository->update($request->all(), $user); 68 | 69 | return response()->json(['msg' => trans('admin.user.edit_ok')]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/OAuthController.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 31 | } 32 | 33 | public function login($name, Request $request) 34 | { 35 | $plugin = app(PluginCenter::class)->get($name); 36 | if (is_null($plugin)) { 37 | return response('', 404); 38 | } 39 | 40 | $request->session()->put('referrer.oauth', $request->server('HTTP_REFERER', route('home'))); 41 | 42 | /* @var Plugin $plugin */ 43 | return $plugin->gotoAuthUrl($request, route('oauth.callback', ['name' => $name])); 44 | } 45 | 46 | public function callback($name, Request $request) 47 | { 48 | $plugin = app(PluginCenter::class)->get($name); 49 | if (is_null($plugin)) { 50 | return response('', 404); 51 | } 52 | 53 | /* @var Plugin $plugin */ 54 | $oauthUser = $plugin->getOAuthUser($request, route('oauth.callback', ['name' => $name])); 55 | $bindUser = null; 56 | foreach ($oauthUser->getBinds() as $type => $id) { 57 | if (!$id) { 58 | continue; 59 | } 60 | $bindUser = $this->userRepository->getUserByOauthId($type, $id); 61 | if ($bindUser) { 62 | break; 63 | } 64 | } 65 | 66 | if ($request->user()) { 67 | return $this->bind($request, $oauthUser, $bindUser); 68 | } else { 69 | return $this->regOrLogin($request, $oauthUser, $bindUser); 70 | } 71 | } 72 | 73 | protected function regOrLogin(Request $request, OAuthUser $oauthUser, User $bindUser = null) 74 | { 75 | //register 76 | if (!$bindUser) { 77 | if (config('cas_server.allow_register')) { 78 | $request->session()->set('oauth', $oauthUser); 79 | 80 | return redirect(route('register.get')); 81 | } else { 82 | return $this->showMessage(trans('not allowed to register')); 83 | } 84 | } 85 | 86 | //refresh db oauth profile 87 | $resp = $this->bind($request, $oauthUser, $bindUser); 88 | \Auth::guard()->login($bindUser); 89 | 90 | return $resp; 91 | } 92 | 93 | protected function bind(Request $request, OAuthUser $oauthUser, User $bindUser = null) 94 | { 95 | $user = $request->user() ?: $bindUser; 96 | if (!$user) { 97 | throw new InvalidArgumentException('logged-in user and bind user can not be null at the same time'); 98 | } 99 | 100 | if ($bindUser && $request->user() && $bindUser->id != $request->user()->id) { 101 | throw new BindOauthFailedException(trans('your oauth account has been bind to another account')); 102 | } 103 | 104 | try { 105 | foreach ($oauthUser->getBinds() as $type => $id) { 106 | $this->userRepository->bindOauth($user, $type, $id, $oauthUser->getOriginal()); 107 | } 108 | } catch (BindOauthFailedException $e) { 109 | //unbind 110 | foreach ($oauthUser->getBinds() as $type => $id) { 111 | $this->userRepository->bindOauth($user, $type, null, null); 112 | } 113 | 114 | return $this->showMessage(trans('already bind by another account')); 115 | } 116 | 117 | return redirect($this->getReferrerUrl($request)); 118 | } 119 | 120 | /** 121 | * @param Request $request 122 | * @return string 123 | */ 124 | protected function getReferrerUrl(Request $request) 125 | { 126 | return $request->session()->pull('referrer.oauth', route('home')); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 29 | } 30 | 31 | public function show(Request $request) 32 | { 33 | $oauth = $request->session()->get('oauth', null); 34 | $referrer = $request->server('HTTP_REFERER', route('home')); 35 | if ($oauth) { 36 | $referrer = $request->session()->pull('referrer.oauth', route('home')); 37 | } 38 | $request->session()->put('referrer.register', $referrer); 39 | 40 | return view('auth.register', ['oauth' => $oauth]); 41 | } 42 | 43 | public function register(Request $request) 44 | { 45 | $this->validate( 46 | $request, 47 | [ 48 | 'password' => $this->userRepository->getPasswordRule(true), 49 | ] 50 | ); 51 | $user = $this->userRepository->create($request->only(['real_name', 'email', 'name', 'password'])); 52 | $oauth = $request->session()->get('oauth'); 53 | if ($oauth) { 54 | /* @var OAuthUser $oauth */ 55 | try { 56 | foreach ($oauth->getBinds() as $type => $id) { 57 | $this->userRepository->bindOauth($user, $type, $id, $oauth->getOriginal()); 58 | } 59 | } catch (BindOauthFailedException $e) { 60 | //FIXME should handle this exception 61 | } 62 | $request->session()->forget('oauth'); 63 | } 64 | 65 | \Auth::guard($this->getGuard())->login($user); 66 | 67 | return redirect($request->session()->pull('referrer.register', route('home'))); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 22 | } 23 | 24 | /** 25 | * Show the application dashboard. 26 | * 27 | * @return \Illuminate\Http\Response 28 | */ 29 | public function indexAction() 30 | { 31 | return view('home'); 32 | } 33 | 34 | public function changePwdAction(Request $request) 35 | { 36 | $rule = $this->userRepository->getPasswordRule(false); 37 | $this->validate($request, ['new' => $rule], [], ['new' => trans('auth.new_pwd')]); 38 | 39 | $old = $request->get('old'); 40 | $new = $request->get('new'); 41 | $user = \Auth::user(); 42 | if (!\Hash::check($old, $user->password)) { 43 | return response()->json(['msg' => trans('message.invalid_old_pwd')], 403); 44 | } 45 | 46 | $this->userRepository->resetPassword($user->id, $new); 47 | 48 | return response()->json(['msg' => trans('message.change_pwd_ok')]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Controllers/PasswordController.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 36 | $this->subject = trans('passwords.email_subject'); 37 | $this->redirectPath = route('home'); 38 | } 39 | 40 | protected function getResetValidationRules() 41 | { 42 | $rule = $this->originGetResetValidationRules(); 43 | $rule['password'] = $this->userRepository->getPasswordRule(true); 44 | 45 | return $rule; 46 | } 47 | 48 | protected function getResetValidationCustomAttributes() 49 | { 50 | return [ 51 | 'password' => trans('auth.new_pwd'), 52 | 'email' => trans('passwords.email'), 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 30 | \App\Http\Middleware\EncryptCookies::class, 31 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 32 | \Illuminate\Session\Middleware\StartSession::class, 33 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 34 | \App\Http\Middleware\VerifyCsrfToken::class, 35 | ], 36 | 37 | 'api' => [ 38 | 'throttle:60,1', 39 | ], 40 | ]; 41 | 42 | /** 43 | * The application's route middleware. 44 | * 45 | * These middleware may be assigned to groups or used individually. 46 | * 47 | * @var array 48 | */ 49 | protected $routeMiddleware = [ 50 | 'admin' => Admin::class, 51 | 'auth' => \App\Http\Middleware\Authenticate::class, 52 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 53 | 'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class, 54 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 55 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 56 | ]; 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Middleware/Admin.php: -------------------------------------------------------------------------------- 1 | user() && $request->user()->admin) { 19 | return $next($request); 20 | } 21 | 22 | if ($request->ajax() || $request->wantsJson()) { 23 | return response('Unauthorized.', 401); 24 | } 25 | 26 | return redirect(cas_route('login.get')); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | guest() || !Auth::guard($guard)->user()->enabled) { 21 | if (Auth::guard($guard)->user()) { 22 | Auth::guard($guard)->logout(); 23 | } 24 | if ($request->ajax() || $request->wantsJson()) { 25 | return response('Unauthorized.', 401); 26 | } 27 | 28 | return redirect()->guest(cas_route('login.get')); 29 | } 30 | 31 | return $next($request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 'auth', 17 | ], 18 | function () { 19 | Route::get('/', ['as' => 'home', 'uses' => 'HomeController@indexAction']); 20 | Route::post('changePwd', ['as' => 'password.change.post', 'uses' => 'HomeController@changePwdAction']); 21 | } 22 | ); 23 | 24 | Route::get('oauth/{name}', ['as' => 'oauth.login', 'uses' => 'Auth\OAuthController@login']); 25 | Route::get('oauth/{name}/callback', ['as' => 'oauth.callback', 'uses' => 'Auth\OAuthController@callback']); 26 | 27 | if (config('cas_server.allow_reset_pwd')) { 28 | Route::group( 29 | [ 30 | 'middleware' => 'guest', 31 | ], 32 | function () { 33 | Route::get( 34 | 'password/email', 35 | ['as' => 'password.reset.request.get', 'uses' => 'PasswordController@getEmail'] 36 | ); 37 | Route::post( 38 | 'password/email', 39 | ['as' => 'password.reset.request.post', 'uses' => 'PasswordController@sendResetLinkEmail'] 40 | ); 41 | Route::get( 42 | 'password/reset/{token?}', 43 | ['as' => 'password.reset.get', 'uses' => 'PasswordController@showResetForm'] 44 | ); 45 | Route::post('password/reset', ['as' => 'password.reset.post', 'uses' => 'PasswordController@reset']); 46 | } 47 | ); 48 | } 49 | 50 | if (config('cas_server.allow_register')) { 51 | Route::group( 52 | [ 53 | 'middleware' => 'guest', 54 | 'namespace' => 'Auth', 55 | ], 56 | function () { 57 | Route::get('register', ['as' => 'register.get', 'uses' => 'RegisterController@show']); 58 | Route::post('register', ['as' => 'register.post', 'uses' => 'RegisterController@postRegister']); 59 | } 60 | ); 61 | } 62 | 63 | Route::group( 64 | [ 65 | 'namespace' => 'Admin', 66 | 'middleware' => 'admin', 67 | 'prefix' => 'admin', 68 | ], 69 | function () { 70 | Route::get('home', ['as' => 'admin_home', 'uses' => 'HomeController@indexAction']); 71 | 72 | Route::resource( 73 | 'user', 74 | 'UserController', 75 | [ 76 | 'only' => ['index', 'store', 'update'], 77 | 'names' => [ 78 | 'index' => 'admin.user.index', 79 | 'store' => 'admin.user.store', 80 | 'update' => 'admin.user.update', 81 | ], 82 | ] 83 | ); 84 | 85 | Route::resource( 86 | 'service', 87 | 'ServiceController', 88 | [ 89 | 'only' => ['index', 'store', 'update'], 90 | 'names' => [ 91 | 'index' => 'admin.service.index', 92 | 'store' => 'admin.service.store', 93 | 'update' => 'admin.service.update', 94 | ], 95 | ] 96 | ); 97 | } 98 | ); -------------------------------------------------------------------------------- /app/Interactions/UserLogin.php: -------------------------------------------------------------------------------- 1 | getCredentials($request); 35 | $credentials['enabled'] = true; 36 | if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) { 37 | return Auth::guard($this->getGuard())->user(); 38 | } 39 | 40 | return null; 41 | } 42 | 43 | /** 44 | * @param Request $request 45 | * @return Response 46 | */ 47 | public function showAuthenticateFailed(Request $request) 48 | { 49 | return $this->sendFailedLoginResponse($request); 50 | } 51 | 52 | /** 53 | * @param Request $request 54 | * @return UserModel|null 55 | */ 56 | public function getCurrentUser(Request $request) 57 | { 58 | return $request->user(); 59 | } 60 | 61 | /** 62 | * @param Request $request 63 | * @param string $jumpUrl 64 | * @param string $service 65 | * @return Response 66 | */ 67 | public function showLoginWarnPage(Request $request, $jumpUrl, $service) 68 | { 69 | return view('auth.login_warn', ['url' => $jumpUrl, 'service' => $service]); 70 | } 71 | 72 | /** 73 | * @param Request $request 74 | * @param array $errors 75 | * @return Response 76 | */ 77 | public function showLoginPage(Request $request, array $errors = []) 78 | { 79 | return view( 80 | 'auth.login', 81 | [ 82 | 'errorMsgs' => $errors, 83 | 'plugins' => app(PluginCenter::class)->getAll(), 84 | 'service' => $request->get('service', null), 85 | ] 86 | ); 87 | } 88 | 89 | /** 90 | * @param array $errors 91 | * @return Response 92 | */ 93 | public function redirectToHome(array $errors = []) 94 | { 95 | return redirect()->intended(route('home'))->withErrors(['global' => $errors]); 96 | } 97 | 98 | /** 99 | * @param Request $request 100 | * @return void 101 | */ 102 | public function logout(Request $request) 103 | { 104 | Auth::guard($this->getGuard())->logout(); 105 | } 106 | 107 | /** 108 | * @param Request $request 109 | * @return Response 110 | */ 111 | public function showLoggedOut(Request $request) 112 | { 113 | return view('auth.logged_out'); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/Models/UserOauth.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'user_id', 'id'); 19 | } 20 | 21 | public function getProfileAttribute() 22 | { 23 | return json_decode($this->attributes['profile'], true); 24 | } 25 | 26 | public function setProfileAttribute($value) 27 | { 28 | $this->attributes['profile'] = json_encode($value); 29 | } 30 | 31 | public function setProfile($fieldName, $value) 32 | { 33 | $profile = $this->getProfileAttribute(); 34 | if ($value) { 35 | $profile[$fieldName] = $value; 36 | } else { 37 | unset($profile[$fieldName]); 38 | } 39 | $this->setProfileAttribute($profile); 40 | } 41 | 42 | public function getPluginsAttribute() 43 | { 44 | $results = []; 45 | $plugins = app(PluginCenter::class)->getAll(); 46 | foreach ($plugins as $key => $plugin) { 47 | /* @var Plugin $plugin */ 48 | $results[$key] = [ 49 | 'name' => $plugin->getName(), 50 | 'logo' => $plugin->getLogo(), 51 | 'status' => !empty($this->attributes[$plugin->getFieldName()]), 52 | ]; 53 | } 54 | 55 | return $results; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->environment() == 'local') { 37 | $this->app->register(IdeHelperServiceProvider::class); 38 | } 39 | 40 | $this->app->bind(UserLoginInterface::class, UserLogin::class); 41 | $this->app->bind(TicketLockerInterface::class, TickerLocker::class); 42 | $this->app->bind(ServiceRepositoryBase::class, ServiceRepository::class); 43 | $this->app->bind( 44 | LockAbstract::class, 45 | function () { 46 | $conf = config('database.connections.mysql'); 47 | 48 | return new MySqlLock($conf['username'], $conf['password'], $conf['host']); 49 | } 50 | ); 51 | $this->app->singleton( 52 | PluginCenter::class, 53 | function () { 54 | return new PluginCenter(app()->getLocale(), config('app.fallback_locale')); 55 | } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPolicies($gate); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapWebRoutes($router); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @param \Illuminate\Routing\Router $router 51 | * @return void 52 | */ 53 | protected function mapWebRoutes(Router $router) 54 | { 55 | $router->group([ 56 | 'namespace' => $this->namespace, 'middleware' => 'web', 57 | ], function ($router) { 58 | require app_path('Http/routes.php'); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Repositories/ServiceRepository.php: -------------------------------------------------------------------------------- 1 | validate( 28 | $data, 29 | [ 30 | 'name' => 'required|unique:cas_services', 31 | 'hosts' => 'array', 32 | 'hosts.*' => 'unique:cas_service_hosts,host', 33 | 'enabled' => 'required|boolean', 34 | 'allow_proxy' => 'required|boolean', 35 | ] 36 | ); 37 | 38 | \DB::beginTransaction(); 39 | $service = $this->service->create( 40 | [ 41 | 'name' => $data['name'], 42 | 'enabled' => $data['enabled'], 43 | 'allow_proxy' => $data['allow_proxy'], 44 | ] 45 | ); 46 | 47 | foreach ($data['hosts'] as $host) { 48 | $hostModel = $this->serviceHost->newInstance(['host' => $host]); 49 | $hostModel->service()->associate($service); 50 | $hostModel->save(); 51 | } 52 | \DB::commit(); 53 | 54 | return $service; 55 | } 56 | 57 | public function update($data, Service $service) 58 | { 59 | $data = array_only( 60 | $data, 61 | [ 62 | 'hosts', 63 | 'enabled', 64 | 'allow_proxy', 65 | ] 66 | ); 67 | 68 | \DB::beginTransaction(); 69 | 70 | $service->hosts()->delete(); 71 | 72 | $this->validate( 73 | $data, 74 | [ 75 | 'hosts' => 'array', 76 | 'hosts.*' => 'unique:cas_service_hosts,host', 77 | 'enabled' => 'boolean', 78 | 'allow_proxy' => 'boolean', 79 | ] 80 | ); 81 | 82 | $hosts = array_get($data, 'hosts', []); 83 | unset($data['hosts']); 84 | 85 | $service->update($data); 86 | foreach ($hosts as $host) { 87 | $hostModel = $this->serviceHost->newInstance(['host' => $host]); 88 | $hostModel->service()->associate($service); 89 | $hostModel->save(); 90 | } 91 | \DB::commit(); 92 | 93 | return $service; 94 | } 95 | 96 | /** 97 | * @param string $search 98 | * @param int $page 99 | * @param int $limit 100 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 101 | */ 102 | public function getList($search, $page, $limit) 103 | { 104 | /* @var \Illuminate\Database\Query\Builder $query */ 105 | $like = '%'.$search.'%'; 106 | if (!empty($search)) { 107 | $query = $this->service->whereHas( 108 | 'hosts', 109 | function ($query) use ($like) { 110 | $query->where('host', 'like', $like); 111 | } 112 | )->orWhere('name', 'like', $like)->with('hosts'); 113 | } else { 114 | $query = $this->service->with('hosts'); 115 | } 116 | 117 | return $query->orderBy('id', 'desc')->paginate($limit, ['*'], 'page', $page); 118 | } 119 | 120 | public function dashboard() 121 | { 122 | return [ 123 | 'total' => $this->service->count(), 124 | 'enabled' => $this->service->where('enabled', true)->count(), 125 | ]; 126 | } 127 | } -------------------------------------------------------------------------------- /app/Repositories/UserRepository.php: -------------------------------------------------------------------------------- 1 | user = $user; 39 | $this->userOauth = $userOauth; 40 | } 41 | 42 | /** 43 | * @param $id 44 | * @return User|null 45 | */ 46 | public function getUserById($id) 47 | { 48 | return $this->user->find($id); 49 | } 50 | 51 | /** 52 | * @param $name 53 | * @return User|null 54 | */ 55 | public function getUserByName($name) 56 | { 57 | return $this->user->where('name', $name)->first(); 58 | } 59 | 60 | /** 61 | * @param $email 62 | * @return User|null 63 | */ 64 | public function getUserByEmail($email) 65 | { 66 | return $this->user->where('email', $email)->first(); 67 | } 68 | 69 | /** 70 | * @param string $type 71 | * @param string $id 72 | * @return User|null 73 | */ 74 | public function getUserByOauthId($type, $id) 75 | { 76 | return $this->user->whereHas( 77 | 'oauth', 78 | function ($query) use ($type, $id) { 79 | $query->where($type, $id); 80 | } 81 | )->first(); 82 | } 83 | 84 | /** 85 | * @param array $data 86 | * @return User 87 | */ 88 | public function create($data) 89 | { 90 | $data = array_only( 91 | $data, 92 | [ 93 | 'name', 94 | 'email', 95 | 'password', 96 | 'real_name', 97 | 'enabled', 98 | 'admin', 99 | ] 100 | ); 101 | 102 | $this->validate( 103 | $data, 104 | [ 105 | 'real_name' => 'required', 106 | 'email' => 'required|email|unique:users', 107 | 'name' => 'required|unique:users', 108 | 'password' => $this->getPasswordRule(false), 109 | 'enabled' => 'boolean', 110 | 'admin' => 'boolean', 111 | ], 112 | [], 113 | [ 114 | 'name' => trans('admin.user.username'), 115 | 'real_name' => trans('admin.user.real_name'), 116 | 'email' => trans('admin.user.email'), 117 | 'password' => trans('admin.user.password'), 118 | ] 119 | ); 120 | $data['password'] = bcrypt($data['password']); 121 | 122 | $user = $this->user->create($data); 123 | $oauth = $this->userOauth->newInstance([]); 124 | $oauth->user()->associate($user); 125 | $oauth->save(); 126 | 127 | return $user; 128 | } 129 | 130 | /** 131 | * @param array $data 132 | * @param User $user 133 | * @return User 134 | */ 135 | public function update($data, User $user) 136 | { 137 | $data = array_only( 138 | $data, 139 | [ 140 | 'password', 141 | 'real_name', 142 | 'enabled', 143 | 'admin', 144 | ] 145 | ); 146 | 147 | $rule = [ 148 | 'real_name' => 'required', 149 | 'enabled' => 'boolean', 150 | 'admin' => 'boolean', 151 | ]; 152 | 153 | $attr = [ 154 | 'real_name' => trans('admin.user.real_name'), 155 | ]; 156 | 157 | if (!empty($data['password'])) { 158 | $rule['password'] = $this->getPasswordRule(false); 159 | $attr['password'] = trans('admin.user.password'); 160 | $data['password'] = bcrypt($data['password']); 161 | } else { 162 | unset($data['password']); 163 | } 164 | 165 | $this->validate($data, $rule, [], $attr); 166 | $user->update($data); 167 | 168 | return $user; 169 | } 170 | 171 | /** 172 | * @param User $user 173 | * @param string $type 174 | * @param string|null $oauthId 175 | * @param array|null $profile 176 | * @return User 177 | * @throws BindOauthFailedException 178 | */ 179 | public function bindOauth(User $user, $type, $oauthId, $profile) 180 | { 181 | if ($oauthId) { 182 | $oldUser = $this->getUserByOauthId($type, $oauthId); 183 | if ($oldUser && $oldUser->id != $user->id) { 184 | throw new BindOauthFailedException(); 185 | } 186 | } 187 | 188 | $user->oauth->{$type} = $oauthId; 189 | $user->oauth->setProfile($type, $profile); 190 | $user->oauth->save(); 191 | 192 | return $user; 193 | } 194 | 195 | /** 196 | * @param int $id 197 | * @param string $pwd 198 | * @throws UserException 199 | * @return User 200 | */ 201 | public function resetPassword($id, $pwd) 202 | { 203 | $user = $this->user->find($id); 204 | if (!$user) { 205 | throw new UserException(trans('messages.user.not_exists')); 206 | } 207 | $user->password = bcrypt($pwd); 208 | $user->remember_token = Str::random(60); 209 | $user->save(); 210 | 211 | return $user; 212 | } 213 | 214 | /** 215 | * @param $search 216 | * @param $enabled 217 | * @param $admin 218 | * @param $page 219 | * @param $limit 220 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 221 | */ 222 | public function getList($search, $enabled, $admin, $page, $limit) 223 | { 224 | /* @var \Illuminate\Database\Query\Builder $query */ 225 | $query = $this->user->query(); 226 | if ($search) { 227 | $like = '%'.$search.'%'; 228 | $query->where( 229 | function ($query) use ($like) { 230 | /* @var \Illuminate\Database\Query\Builder $query */ 231 | $query->where('name', 'like', $like) 232 | ->orWhere('real_name', 'like', $like) 233 | ->orWhere('email', 'like', $like); 234 | } 235 | ); 236 | } 237 | 238 | if (!is_null($enabled)) { 239 | $query->where('enabled', boolval($enabled)); 240 | } 241 | 242 | if (!is_null($admin)) { 243 | $query->where('admin', boolval($admin)); 244 | } 245 | 246 | return $query->orderBy('id', 'desc')->paginate($limit, ['*'], 'page', $page); 247 | } 248 | 249 | /** 250 | * @param bool $needConfirmed 251 | * @return string 252 | */ 253 | public function getPasswordRule($needConfirmed = false) 254 | { 255 | $rule = [ 256 | 'required', 257 | 'min:6', 258 | ]; 259 | if ($needConfirmed) { 260 | $rule[] = 'confirmed'; 261 | } 262 | 263 | return join('|', $rule); 264 | } 265 | 266 | public function dashboard() 267 | { 268 | return [ 269 | 'total' => $this->user->count(), 270 | 'active' => $this->user->where('enabled', true)->count(), 271 | 'admin' => $this->user->where('admin', true)->count(), 272 | ]; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /app/Services/TickerLocker.php: -------------------------------------------------------------------------------- 1 | locker = $locker; 28 | } 29 | 30 | public function acquireLock($key, $timeout) 31 | { 32 | return $this->locker->acquireLock($key, $timeout); 33 | } 34 | 35 | public function releaseLock($key) 36 | { 37 | return $this->locker->releaseLock($key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Traits/Response/ShowMessage.php: -------------------------------------------------------------------------------- 1 | $message, 18 | 'subMsg' => $subMsg, 19 | 'btnName' => $btnName ?: trans('message.back_to_home'), 20 | 'btnLink' => $btnLink ?: route('home'), 21 | ]; 22 | 23 | return view('common.message', $data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Traits/ValidateInput.php: -------------------------------------------------------------------------------- 1 | fails()) { 28 | return []; 29 | } 30 | 31 | if ($throws) { 32 | throw new ValidationException($validator); 33 | } 34 | 35 | return $validator->errors(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | 'boolean', 38 | 'admin' => 'boolean', 39 | ]; 40 | 41 | public function oauth() 42 | { 43 | return $this->hasOne(UserOauth::class); 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getName() 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * @return $this 56 | */ 57 | public function getEloquentModel() 58 | { 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getCASAttributes() 66 | { 67 | return [ 68 | 'email' => $this->email, 69 | 'real_name' => $this->real_name, 70 | 'oauth_profile' => json_encode($this->oauth->profile), 71 | ]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | getRoutes()->getByName($name)->getUri(); 16 | } 17 | 18 | /** 19 | * @param $name 20 | * @return string 21 | */ 22 | function cas_route_uri($name) 23 | { 24 | $name = config('cas.router.name_prefix').$name; 25 | 26 | return route_uri($name); 27 | } 28 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | =5.5.9", 9 | "laravel/framework": "5.2.*", 10 | "leo108/laravel_cas_server": "^2.0.0", 11 | "leo108/php_cas_server_oauth_plugin_center": "2.*", 12 | "arvenil/ninja-mutex": "^0.5.1", 13 | "fideloper/proxy": "^3.1" 14 | }, 15 | "require-dev": { 16 | "fzaninotto/faker": "~1.4", 17 | "mockery/mockery": "0.9.*", 18 | "phpunit/phpunit": "~4.0", 19 | "symfony/css-selector": "2.8.*|3.0.*", 20 | "symfony/dom-crawler": "2.8.*|3.0.*", 21 | "barryvdh/laravel-ide-helper": "^2.2", 22 | "doctrine/dbal": "^2.5" 23 | }, 24 | "autoload": { 25 | "classmap": [ 26 | "database" 27 | ], 28 | "psr-4": { 29 | "App\\": "app/" 30 | }, 31 | "files": [ 32 | "app/helpers.php" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "classmap": [ 37 | "tests/TestCase.php" 38 | ] 39 | }, 40 | "scripts": { 41 | "post-root-package-install": [ 42 | "php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 43 | ], 44 | "post-create-project-cmd": [ 45 | "php artisan key:generate" 46 | ], 47 | "post-install-cmd": [ 48 | "Illuminate\\Foundation\\ComposerScripts::postInstall", 49 | "php artisan optimize" 50 | ], 51 | "post-update-cmd": [ 52 | "Illuminate\\Foundation\\ComposerScripts::postUpdate", 53 | "php artisan ide-helper:generate", 54 | "php artisan ide-helper:meta", 55 | "php artisan optimize" 56 | ] 57 | }, 58 | "config": { 59 | "preferred-install": "dist" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => App\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | Here you may set the options for resetting passwords including the view 85 | | that is your password reset e-mail. You may also set the name of the 86 | | table that maintains all of the reset tokens for your application. 87 | | 88 | | You may specify multiple password reset configurations if you have more 89 | | than one user table or model in the application and you want to have 90 | | separate password reset settings based on the specific user types. 91 | | 92 | | The expire time is the number of minutes that the reset token should be 93 | | considered valid. This security feature keeps tokens short-lived so 94 | | they have less time to be guessed. You may change this as needed. 95 | | 96 | */ 97 | 98 | 'passwords' => [ 99 | 'users' => [ 100 | 'provider' => 'users', 101 | 'email' => 'auth.emails.password', 102 | 'table' => 'password_resets', 103 | 'expire' => 60, 104 | ], 105 | ], 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'pusher'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_KEY'), 36 | 'secret' => env('PUSHER_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | // 40 | ], 41 | ], 42 | 43 | 'redis' => [ 44 | 'driver' => 'redis', 45 | 'connection' => 'default', 46 | ], 47 | 48 | 'log' => [ 49 | 'driver' => 'log', 50 | ], 51 | 52 | ], 53 | 54 | ]; 55 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | */ 30 | 31 | 'stores' => [ 32 | 33 | 'apc' => [ 34 | 'driver' => 'apc', 35 | ], 36 | 37 | 'array' => [ 38 | 'driver' => 'array', 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => 'cache', 44 | 'connection' => null, 45 | ], 46 | 47 | 'file' => [ 48 | 'driver' => 'file', 49 | 'path' => storage_path('framework/cache'), 50 | ], 51 | 52 | 'memcached' => [ 53 | 'driver' => 'memcached', 54 | 'servers' => [ 55 | [ 56 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 57 | 'port' => env('MEMCACHED_PORT', 11211), 58 | 'weight' => 100, 59 | ], 60 | ], 61 | ], 62 | 63 | 'redis' => [ 64 | 'driver' => 'redis', 65 | 'connection' => 'default', 66 | ], 67 | 68 | ], 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Cache Key Prefix 73 | |-------------------------------------------------------------------------- 74 | | 75 | | When utilizing a RAM based store such as APC or Memcached, there might 76 | | be other applications utilizing the same cache. So, we'll specify a 77 | | value to get prefixed to all our keys so we can avoid collisions. 78 | | 79 | */ 80 | 81 | 'prefix' => 'laravel', 82 | 83 | ]; 84 | -------------------------------------------------------------------------------- /config/cas.php: -------------------------------------------------------------------------------- 1 | env('CAS_LOCK_TIMEOUT', 5000), 5 | 'ticket_expire' => env('CAS_TICKET_EXPIRE', 300), 6 | 'ticket_len' => env('CAS_TICKET_LEN', 32), 7 | 'pg_ticket_expire' => env('CAS_PROXY_GRANTING_TICKET_EXPIRE', 7200), 8 | 'pg_ticket_len' => env('CAS_PROXY_GRANTING_TICKET_LEN', 64), 9 | 'pg_ticket_iou_len' => env('CAS_PROXY_GRANTING_TICKET_IOU_LEN', 64), 10 | 'verify_ssl' => env('CAS_VERIFY_SSL', true), 11 | 'user_table' => [ 12 | 'id' => 'id', 13 | 'name' => 'users', 14 | 'model' => \App\User::class, //change to your user model class 15 | ], 16 | 'router' => [ 17 | 'prefix' => 'cas', 18 | 'name_prefix' => 'cas.', 19 | ], 20 | 'middleware' => [ 21 | 'common' => 'web', 22 | 'auth' => 'auth', 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /config/cas_server.php: -------------------------------------------------------------------------------- 1 | env('CAS_SERVER_NAME', 'Central Authentication Service'), 5 | 'allow_reset_pwd' => env('CAS_SERVER_ALLOW_RESET_PWD', true), 6 | 'allow_register' => env('CAS_SERVER_ALLOW_REGISTER', true), 7 | 'disable_pwd_login' => env('CAS_SERVER_DISABLE_PASSWORD_LOGIN', false), 8 | ]; 9 | -------------------------------------------------------------------------------- /config/compile.php: -------------------------------------------------------------------------------- 1 | [ 17 | // 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled File Providers 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may list service providers which define a "compiles" function 26 | | that returns additional files that should be compiled, providing an 27 | | easy way to get common files from any packages you are utilizing. 28 | | 29 | */ 30 | 31 | 'providers' => [ 32 | // 33 | ], 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => env('DB_CONNECTION', 'mysql'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => [ 48 | 49 | 'sqlite' => [ 50 | 'driver' => 'sqlite', 51 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 52 | 'prefix' => '', 53 | ], 54 | 55 | 'mysql' => [ 56 | 'driver' => 'mysql', 57 | 'host' => env('DB_HOST', 'localhost'), 58 | 'port' => env('DB_PORT', '3306'), 59 | 'database' => env('DB_DATABASE', 'forge'), 60 | 'username' => env('DB_USERNAME', 'forge'), 61 | 'password' => env('DB_PASSWORD', ''), 62 | 'charset' => 'utf8', 63 | 'collation' => 'utf8_unicode_ci', 64 | 'prefix' => '', 65 | 'strict' => false, 66 | 'engine' => null, 67 | ], 68 | 69 | 'pgsql' => [ 70 | 'driver' => 'pgsql', 71 | 'host' => env('DB_HOST', 'localhost'), 72 | 'port' => env('DB_PORT', '5432'), 73 | 'database' => env('DB_DATABASE', 'forge'), 74 | 'username' => env('DB_USERNAME', 'forge'), 75 | 'password' => env('DB_PASSWORD', ''), 76 | 'charset' => 'utf8', 77 | 'prefix' => '', 78 | 'schema' => 'public', 79 | ], 80 | 81 | ], 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Migration Repository Table 86 | |-------------------------------------------------------------------------- 87 | | 88 | | This table keeps track of all the migrations that have already run for 89 | | your application. Using this information, we can determine which of 90 | | the migrations on disk haven't actually been run in the database. 91 | | 92 | */ 93 | 94 | 'migrations' => 'migrations', 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Redis Databases 99 | |-------------------------------------------------------------------------- 100 | | 101 | | Redis is an open source, fast, and advanced key-value store that also 102 | | provides a richer set of commands than a typical key-value systems 103 | | such as APC or Memcached. Laravel makes it easy to dig right in. 104 | | 105 | */ 106 | 107 | 'redis' => [ 108 | 109 | 'cluster' => false, 110 | 111 | 'default' => [ 112 | 'host' => env('REDIS_HOST', 'localhost'), 113 | 'password' => env('REDIS_PASSWORD', null), 114 | 'port' => env('REDIS_PORT', 6379), 115 | 'database' => 0, 116 | ], 117 | 118 | ], 119 | 120 | ]; 121 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'local', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Default Cloud Filesystem Disk 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Many applications store files both locally and in the cloud. For this 26 | | reason, you may specify a default "cloud" driver here. This driver 27 | | will be bound as the Cloud disk implementation in the container. 28 | | 29 | */ 30 | 31 | 'cloud' => 's3', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Filesystem Disks 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here you may configure as many filesystem "disks" as you wish, and you 39 | | may even configure multiple disks of the same driver. Defaults have 40 | | been setup for each driver as an example of the required options. 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'visibility' => 'public', 55 | ], 56 | 57 | 's3' => [ 58 | 'driver' => 's3', 59 | 'key' => 'your-key', 60 | 'secret' => 'your-secret', 61 | 'region' => 'your-region', 62 | 'bucket' => 'your-bucket', 63 | ], 64 | 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | SMTP Host Address 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may provide the host address of the SMTP server used by your 27 | | applications. A default option is provided that is compatible with 28 | | the Mailgun mail service which will provide reliable deliveries. 29 | | 30 | */ 31 | 32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | SMTP Host Port 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the SMTP port used by your application to deliver e-mails to 40 | | users of the application. Like the host we have set this value to 41 | | stay compatible with the Mailgun e-mail application by default. 42 | | 43 | */ 44 | 45 | 'port' => env('MAIL_PORT', 587), 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Global "From" Address 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You may wish for all e-mails sent by your application to be sent from 53 | | the same address. Here, you may specify a name and address that is 54 | | used globally for all e-mails that are sent by your application. 55 | | 56 | */ 57 | 58 | 'from' => ['address' => null, 'name' => null], 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | E-Mail Encryption Protocol 63 | |-------------------------------------------------------------------------- 64 | | 65 | | Here you may specify the encryption protocol that should be used when 66 | | the application send e-mail messages. A sensible default using the 67 | | transport layer security protocol should provide great security. 68 | | 69 | */ 70 | 71 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 72 | 73 | /* 74 | |-------------------------------------------------------------------------- 75 | | SMTP Server Username 76 | |-------------------------------------------------------------------------- 77 | | 78 | | If your SMTP server requires a username for authentication, you should 79 | | set it here. This will get used to authenticate with your server on 80 | | connection. You may also set the "password" value below this one. 81 | | 82 | */ 83 | 84 | 'username' => env('MAIL_USERNAME'), 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | SMTP Server Password 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Here you may set the password required by your SMTP server to send out 92 | | messages from your application. This will be given to the server on 93 | | connection so that the application will be able to send messages. 94 | | 95 | */ 96 | 97 | 'password' => env('MAIL_PASSWORD'), 98 | 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Sendmail System Path 102 | |-------------------------------------------------------------------------- 103 | | 104 | | When using the "sendmail" driver to send e-mails, we will need to know 105 | | the path to where Sendmail lives on this server. A default path has 106 | | been provided here, which will work well on most of your systems. 107 | | 108 | */ 109 | 110 | 'sendmail' => '/usr/sbin/sendmail -bs', 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'expire' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'ttr' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => 'your-public-key', 54 | 'secret' => 'your-secret-key', 55 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 56 | 'queue' => 'your-queue-name', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'expire' => 90, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Failed Queue Jobs 72 | |-------------------------------------------------------------------------- 73 | | 74 | | These options configure the behavior of failed queue job logging so you 75 | | can control which database and table are used to store the jobs that 76 | | have failed. You may change them to any database / table you wish. 77 | | 78 | */ 79 | 80 | 'failed' => [ 81 | 'database' => env('DB_CONNECTION', 'mysql'), 82 | 'table' => 'failed_jobs', 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'mandrill' => [ 23 | 'secret' => env('MANDRILL_SECRET'), 24 | ], 25 | 26 | 'ses' => [ 27 | 'key' => env('SES_KEY'), 28 | 'secret' => env('SES_SECRET'), 29 | 'region' => 'us-east-1', 30 | ], 31 | 32 | 'sparkpost' => [ 33 | 'secret' => env('SPARKPOST_SECRET'), 34 | ], 35 | 36 | 'stripe' => [ 37 | 'model' => App\User::class, 38 | 'key' => env('STRIPE_KEY'), 39 | 'secret' => env('STRIPE_SECRET'), 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'file'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle before it expires. If you want them 28 | | to immediately expire on the browser closing, set that option. 29 | | 30 | */ 31 | 32 | 'lifetime' => 120, 33 | 34 | 'expire_on_close' => false, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Session Encryption 39 | |-------------------------------------------------------------------------- 40 | | 41 | | This option allows you to easily specify that all of your session data 42 | | should be encrypted before it is stored. All encryption will be run 43 | | automatically by Laravel and you can use the Session like normal. 44 | | 45 | */ 46 | 47 | 'encrypt' => false, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Session File Location 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When using the native session driver, we need a location where session 55 | | files may be stored. A default has been set for you but a different 56 | | location may be specified. This is only needed for file sessions. 57 | | 58 | */ 59 | 60 | 'files' => storage_path('framework/sessions'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session Database Connection 65 | |-------------------------------------------------------------------------- 66 | | 67 | | When using the "database" or "redis" session drivers, you may specify a 68 | | connection that should be used to manage these sessions. This should 69 | | correspond to a connection in your database configuration options. 70 | | 71 | */ 72 | 73 | 'connection' => null, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Session Database Table 78 | |-------------------------------------------------------------------------- 79 | | 80 | | When using the "database" session driver, you may specify the table we 81 | | should use to manage the sessions. Of course, a sensible default is 82 | | provided for you; however, you are free to change this as needed. 83 | | 84 | */ 85 | 86 | 'table' => 'sessions', 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Session Sweeping Lottery 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Some session drivers must manually sweep their storage location to get 94 | | rid of old sessions from storage. Here are the chances that it will 95 | | happen on a given request. By default, the odds are 2 out of 100. 96 | | 97 | */ 98 | 99 | 'lottery' => [2, 100], 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Cookie Name 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Here you may change the name of the cookie used to identify a session 107 | | instance by ID. The name specified here will get used every time a 108 | | new session cookie is created by the framework for every driver. 109 | | 110 | */ 111 | 112 | 'cookie' => 'laravel_session', 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Path 117 | |-------------------------------------------------------------------------- 118 | | 119 | | The session cookie path determines the path for which the cookie will 120 | | be regarded as available. Typically, this will be the root path of 121 | | your application but you are free to change this when necessary. 122 | | 123 | */ 124 | 125 | 'path' => '/', 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Session Cookie Domain 130 | |-------------------------------------------------------------------------- 131 | | 132 | | Here you may change the domain of the cookie used to identify a session 133 | | in your application. This will determine which domains the cookie is 134 | | available to in your application. A sensible default has been set. 135 | | 136 | */ 137 | 138 | 'domain' => env('SESSION_DOMAIN', null), 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | HTTPS Only Cookies 143 | |-------------------------------------------------------------------------- 144 | | 145 | | By setting this option to true, session cookies will only be sent back 146 | | to the server if the browser has a HTTPS connection. This will keep 147 | | the cookie from being sent to you if it can not be done securely. 148 | | 149 | */ 150 | 151 | 'secure' => false, 152 | 153 | /* 154 | |-------------------------------------------------------------------------- 155 | | HTTP Access Only 156 | |-------------------------------------------------------------------------- 157 | | 158 | | Setting this value to true will prevent JavaScript from accessing the 159 | | value of the cookie and the cookie will only be accessible through 160 | | the HTTP protocol. You are free to modify this option if needed. 161 | | 162 | */ 163 | 164 | 'http_only' => true, 165 | 166 | ]; 167 | -------------------------------------------------------------------------------- /config/trustedproxy.php: -------------------------------------------------------------------------------- 1 | explode(',', env('TRUSTED_PROXIES')), 17 | 18 | /* 19 | * Or, to trust all proxies, uncomment this: 20 | */ 21 | # 'proxies' => '*', 22 | 23 | /* 24 | * Default Header Names 25 | * 26 | * Change these if the proxy does 27 | * not send the default header names. 28 | * 29 | * Note that headers such as X-Forwarded-For 30 | * are transformed to HTTP_X_FORWARDED_FOR format. 31 | * 32 | * The following are Symfony defaults, found in 33 | * \Symfony\Component\HttpFoundation\Request::$trustedHeaders 34 | */ 35 | 'headers' => [ 36 | \Illuminate\Http\Request::HEADER_CLIENT_IP => env('TRUSTED_HEADER_CLIENT_IP', 'X_FORWARDED_FOR'), 37 | \Illuminate\Http\Request::HEADER_CLIENT_HOST => env('TRUSTED_HEADER_CLIENT_HOST', 'X_FORWARDED_HOST'), 38 | \Illuminate\Http\Request::HEADER_CLIENT_PROTO => env('TRUSTED_HEADER_CLIENT_PROTO', 'X_FORWARDED_PROTO'), 39 | \Illuminate\Http\Request::HEADER_CLIENT_PORT => env('TRUSTED_HEADER_CLIENT_PORT', 'X_FORWARDED_PORT'), 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | realpath(base_path('resources/views')), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker\Generator $faker) { 15 | return [ 16 | 'name' => $faker->name, 17 | 'email' => $faker->safeEmail, 18 | 'password' => bcrypt(str_random(10)), 19 | 'remember_token' => str_random(10), 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name', 100)->collation('utf8mb4_unicode_ci')->unique(); 18 | $table->string('real_name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->boolean('enabled')->default(true); 22 | $table->boolean('admin')->default(false); 23 | $table->rememberToken(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('users'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 17 | $table->string('token')->index(); 18 | $table->timestamp('created_at')->nullable(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::drop('password_resets'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_08_01_061914_create_services_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name')->unique(); 18 | $table->boolean('enabled')->default(true); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('cas_services'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2016_08_01_061918_create_tickets_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('ticket', 32)->unique(); 18 | $table->string('service_url', 1024); 19 | $table->integer('service_id')->unsigned(); 20 | $table->integer('user_id')->unsigned(); 21 | $table->timestamp('created_at')->nullable(); 22 | $table->timestamp('expire_at')->nullable(); 23 | $table->foreign('service_id')->references('id')->on('cas_services'); 24 | $table->foreign('user_id')->references(config('cas.user_table.id'))->on(config('cas.user_table.name')); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('cas_tickets'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2016_08_01_061923_create_service_hosts_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('host')->unique(); 18 | $table->integer('service_id')->unsigned(); 19 | $table->foreign('service_id')->references('id')->on('cas_services'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('cas_service_hosts'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2016_09_20_124536_create_oauth_table.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('user_id')->primary(); 17 | $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('user_oauth'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_09_27_232302_add_profile_to_user_oauth_table.php: -------------------------------------------------------------------------------- 1 | text('profile')->after('user_id')->collation('utf8mb4_unicode_ci'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('user_oauth', function (Blueprint $table) { 28 | $table->dropColumn('profile'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('ticket')->unique(); 18 | $table->string('pgt_url', 1024); 19 | $table->integer('service_id')->unsigned(); 20 | $table->integer('user_id')->unsigned(); 21 | $table->text('proxies')->nullable(); 22 | $table->timestamp('created_at')->nullable(); 23 | $table->timestamp('expire_at')->nullable(); 24 | $table->foreign('service_id')->references('id')->on('cas_services'); 25 | $table->foreign('user_id')->references(config('cas.user_table.id'))->on(config('cas.user_table.name')); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('cas_proxy_granting_tickets'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2016_10_25_083620_add_proxies_to_tickets_table.php: -------------------------------------------------------------------------------- 1 | text('proxies')->nullable()->after('user_id'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('cas_tickets', function (Blueprint $table) { 28 | $table->dropColumn('proxies'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_10_25_092220_add_allow_proxy_to_services_table.php: -------------------------------------------------------------------------------- 1 | boolean('allow_proxy')->default(false)->after('name'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('cas_services', function (Blueprint $table) { 28 | $table->dropColumn('allow_proxy'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_10_29_083634_change_ticket_column_length.php: -------------------------------------------------------------------------------- 1 | string('ticket')->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('cas_tickets', function (Blueprint $table) { 28 | $table->string('ticket', 32)->change(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/seeds/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var elixir = require('laravel-elixir'); 2 | var glob = require('glob'); 3 | 4 | elixir.config.js.browserify.transformers.push({ 5 | name: 'vueify', 6 | options: {} 7 | }); 8 | 9 | var getEntries = function (globPath, replace) { 10 | var entries = {}; 11 | 12 | if (typeof globPath == 'string') { 13 | globPath = [globPath]; 14 | } 15 | 16 | for (var x in globPath) { 17 | glob.sync(globPath[x]).forEach(function (entry) { 18 | var basename = entry.replace(replace, ''); 19 | entries[basename] = entry; 20 | }); 21 | } 22 | return entries; 23 | }; 24 | 25 | elixir(function (mix) { 26 | var browserifyBatch = function (src) { 27 | var base = elixir.config.assetsPath + '/js/'; 28 | var entries = getEntries(base + src, base); 29 | for(var x in entries){ 30 | var p = entries[x].replace(base, ''); 31 | mix.browserify(p, elixir.config.publicPath + '/js/' + x); 32 | } 33 | }; 34 | browserifyBatch('front/**/*.js'); 35 | browserifyBatch('admin/**/*.js'); 36 | mix 37 | .less('sb-admin-2.less') 38 | .sass('app.scss') 39 | .browserify('common/common.js') 40 | .version(['css/**/*.css', 'js/**/*.js']) 41 | .copy('node_modules/font-awesome/fonts', 'public/build/fonts') 42 | ; 43 | }); 44 | -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf public/build public/css public/js build.zip 4 | yarn 5 | npm run prod 6 | git clean -e public -e vendor -e '.env' -e '.idea' -dxf 7 | zip -q -r build.zip . -x ".git/*" -x build.zip -x ".idea/*" -x ".env" 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "prod": "gulp --production", 5 | "dev": "gulp watch" 6 | }, 7 | "devDependencies": { 8 | "bootbox": "^4.4.0", 9 | "bootstrap-sass": "^3.3.7", 10 | "font-awesome": "^4.6.3", 11 | "glob": "^7.0.6", 12 | "gulp": "^3.9.1", 13 | "jquery": "^3.1.0", 14 | "laravel-elixir": "^5.0.0", 15 | "lodash": "^4.16.0", 16 | "metismenu": "^2.5.2", 17 | "vue": "^1.0.26", 18 | "vue-resource": "^1.0.2", 19 | "vueify": "^8.7.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./app 19 | 20 | ./app/Http/routes.php 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leo108/php_cas_server/64360e7795f7399fce481d9106b37fb2f655a28b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels nice to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../bootstrap/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PHP CAS Server 2 | 3 | PHP CAS Server is a PHP implementation of CAS Server Protocol based on Laravel. 4 | 5 | [中文文档](https://github.com/leo108/php_cas_server/blob/master/readme_zh.md) 6 | 7 | ## Features 8 | 9 | * [CAS protocol](https://apereo.github.io/cas/4.2.x/protocol/CAS-Protocol-Specification.html) v1/v2/v3 (proxy is supported now!). 10 | * User management, including adding/editing/searching users, enable/disable users, set/unset as administrator. 11 | * Service management, including adding/editing/searching services, enable/disable services. 12 | * I18n, support English and Chinese out of box, you can add language as your need. 13 | * Customize login methods, support email + password by default, you can add custom login methods by plugins. You can also disable email login by settings. 14 | 15 | ## Requirements 16 | 17 | * PHP >= 5.5.9 18 | 19 | ## Installation 20 | 21 | ### By composer (Recommend) 22 | 23 | 1. `composer create-project leo108/php_cas_server php_cas_server dev-master` 24 | 2. `npm install` or `yarn` 25 | 3. `gulp` 26 | 27 | ### By release tarballs 28 | 29 | [Download Link](https://github.com/leo108/php_cas_server/releases) 30 | 31 | ## Configuration 32 | 33 | If you install by tarball, you have to copy `.env.example` to `.env`, and then run `php artisan key:generate` 34 | 35 | All settings are in `.env` file. 36 | 37 | ### Basic 38 | 39 | |Field|Default Value|Description| 40 | |-----|-----|---| 41 | |APP_ENV|`local`|running environment,use `local` if in development, use `production` in production| 42 | |APP_KEY|random value|left as is| 43 | |APP_DEBUG|`true`|enable debug mode, set to `false` to disable| 44 | |APP_LOG_LEVEL|`debug`|log level, `debug`/`info`/`notice`/`warning`/`error`/`critical`/`alert`/`emergency`| 45 | |APP_URL|`http://localhost`|your app's url, needs `http(s)://` at the beginning| 46 | |APP_LOCALE|`en`|language, support `en` and `cn` out of box| 47 | 48 | ### Database 49 | 50 | You have to set all fields that begin with `DB_`, then run `php artisan migrate` to initial database schema. 51 | 52 | ### CAS Server 53 | 54 | |Field|Default Value|Description| 55 | |-----|-----|---| 56 | |CAS_LOCK_TIMEOUT|`5000`|CAS ticket locking time, in milliseconds| 57 | |CAS_TICKET_EXPIRE|`300`|CAS ticket expire time, in seconds| 58 | |CAS_TICKET_LEN|`32`|CAS ticket length, it's recommend at least 32| 59 | |CAS_PROXY_GRANTING_TICKET_EXPIRE|`7200`|CAS proxy-granting ticket expire time, in seconds| 60 | |CAS_PROXY_GRANTING_TICKET_LEN|`64`|CAS proxy-granting ticket length, it's recommend at least 64| 61 | |CAS_PROXY_GRANTING_TICKET_IOU_LEN|`64`|CAS proxy-granting ticket IOU length, it's recommend at least 64| 62 | |CAS_VERIFY_SSL|`true`|Whether to check ssl when calling pgt url| 63 | |CAS_SERVER_ALLOW_RESET_PWD|`true`|allow user reset password by email| 64 | |CAS_SERVER_ALLOW_REGISTER|`true`|allow user register| 65 | |CAS_SERVER_DISABLE_PASSWORD_LOGIN|`false`|disable password login| 66 | |CAS_SERVER_NAME|`Central Authentication Service`|The site name of your CAS Server| 67 | 68 | ### Setup behind reverse proxy 69 | 70 | |Field|Default Value|Description| 71 | |-----|-----|---| 72 | |TRUSTED_PROXIES|`127.0.0.1`|The IP of reserve proxy servers, separated by comma(`,`), you can specific IP or use s subnet such as `127.0.0.1` and `127.0.0.1/24`, configurations below take effect only when visiting IP in this list| 73 | |TRUSTED_HEADER_CLIENT_IP|`X_FORWARDED_FOR`|User's real IP is stored in this request header| 74 | |TRUSTED_HEADER_CLIENT_HOST|`X_FORWARDED_HOST`|The host user visited is stored in this request header| 75 | |TRUSTED_HEADER_CLIENT_PROTO|`X_FORWARDED_PROTO`|The http protocol user used is stored in this request header| 76 | |TRUSTED_HEADER_CLIENT_PORT|`X_FORWARDED_PORT`|The port user visited is stored in this request header| 77 | 78 | ## Initial database and create administrator 79 | 80 | Execute `php artisan migrate` at the root directory of this project to initial database. 81 | 82 | Execute `php artisan make:admin --password=yourpassword` to create an administrator account. 83 | 84 | ## License 85 | 86 | [MIT](http://opensource.org/licenses/MIT). 87 | -------------------------------------------------------------------------------- /readme_zh.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | PHP CAS Server是一个基于Laravel框架开发的CAS服务端实现,旨在解决使用PHP技术栈的中小型公司因无法对Java版CAS服务端二次开发而放弃使用CAS的问题,因此本项目的核心目标之一就是易于扩展。 4 | 5 | # 功能 6 | 7 | * 目前已经实现了CAS协议v1/v2/v3版本的服务端核心逻辑(现在已经支持Proxy相关的接口!)。 8 | * 用户管理,包含新增、修改、搜索用户,启用、禁用用户,设置、取消管理员。 9 | * 服务管理,包含新增、修改、搜索服务,启用、禁用服务。 10 | * 国际化,默认支持中文和英文,可自行添加语言包。 11 | * 登录方式插件化,默认支持邮箱+密码登录,可通过插件新增登录方式,如微信登录,也可以通过配置关闭密码登录功能。 12 | 13 | # 运行环境 14 | 15 | * php >= 5.5.9 16 | 17 | # 安装 18 | 19 | ## 通过composer安装 20 | 21 | 1. `composer create-project leo108/php_cas_server php_cas_server dev-master` 22 | 2. `npm install` 或者 `yarn` 23 | 3. `gulp` 24 | 25 | ## 下载压缩包 26 | 27 | [压缩包列表](https://github.com/leo108/php_cas_server/releases) 28 | 29 | # 配置 30 | 31 | 如果是通过压缩包方式安装,需要先将根目录下的`.env.example`文件复制一份并命名为`.env`,然后执行`php artisan key:generate`。 32 | 33 | 所有的配置均通过修改`.env`文件来完成。 34 | 35 | ## 基础配置 36 | 37 | |配置项|默认值|说明| 38 | |-----|-----|---| 39 | |APP_ENV|`local`|运行环境,`local`代表本地开发环境,`production`代表线上环境| 40 | |APP_KEY|随机值|加密所需的key,不需要修改| 41 | |APP_DEBUG|`true`|是否开启Debug模式,设置成`false`关闭| 42 | |APP_LOG_LEVEL|`debug`|日志级别,可选项有`debug`/`info`/`notice`/`warning`/`error`/`critical`/`alert`/`emergency`| 43 | |APP_URL|`http://localhost`|访问网址,需要在前面加上`http(s)://`| 44 | |APP_LOCALE|`en`|语言,默认提供`en`和`cn`| 45 | 46 | --- 47 | 48 | ## 数据库配置 49 | 50 | 修改所有以`DB_`开头的配置项。设置完成之后需要执行`php artisan migrate`来完成数据库的初始化。 51 | 52 | ## CAS配置 53 | 54 | |配置项|默认值|说明| 55 | |-----|-----|---| 56 | |CAS_LOCK_TIMEOUT|`5000`|CAS令牌锁定时间,单位毫秒| 57 | |CAS_TICKET_EXPIRE|`300`|CAS令牌过期时间,单位秒| 58 | |CAS_TICKET_LEN|`32`|CAS令牌长度,推荐不低于32| 59 | |CAS_PROXY_GRANTING_TICKET_EXPIRE|`7200`|CAS代理令牌过期时间,单位秒| 60 | |CAS_PROXY_GRANTING_TICKET_LEN|`64`|CAS proxy-granting令牌长度,推荐不低于64| 61 | |CAS_PROXY_GRANTING_TICKET_IOU_LEN|`64`|CAS proxy-granting ticket IOU长度,推荐不低于64| 62 | |CAS_VERIFY_SSL|`true`|在回调PGT Url的时候是否校验ssl,测试时可以关闭,线上环境强烈建议打开此选项| 63 | |CAS_SERVER_ALLOW_RESET_PWD|`true`|是否允许用户通过邮箱找回密码| 64 | |CAS_SERVER_ALLOW_REGISTER|`true`|是否允许用户注册| 65 | |CAS_SERVER_DISABLE_PASSWORD_LOGIN|`false`|是否禁用密码登录| 66 | |CAS_SERVER_NAME|`Central Authentication Service`|CAS Server的站点名称(用于展示)| 67 | 68 | ## 反向代理配置 69 | 70 | 当PHP CAS Server运行在反向代理后面时,需要设置相关配置才可正常运行 71 | 72 | |配置项|默认值|说明| 73 | |-----|-----|---| 74 | |TRUSTED_PROXIES|`127.0.0.1`|反向代理服务器ip,多个用英文逗号`,`隔开,支持指定ip:`127.0.0.1`和子网:`127.0.0.1/24`,以下配置只有访问ip在这个列表中时才生效。| 75 | |TRUSTED_HEADER_CLIENT_IP|`X_FORWARDED_FOR`|该请求头保存用户的ip| 76 | |TRUSTED_HEADER_CLIENT_HOST|`X_FORWARDED_HOST`|该请求头保存用户访问的域名| 77 | |TRUSTED_HEADER_CLIENT_PROTO|`X_FORWARDED_PROTO`|该请求头保存用户访问时使用的协议| 78 | |TRUSTED_HEADER_CLIENT_PORT|`X_FORWARDED_PORT`|该请求头保存用户访问的端口| 79 | 80 | ## 初始化数据库 && 创建管理员 81 | 82 | 在项目的根目录执行`php artisan migrate`来初始化数据库结构。 83 | 84 | 执行`php artisan make:admin --password=yourpassword`创建一个管理员账号。 85 | 86 | # 登录插件 87 | 88 | 对于使用压缩包安装的用户,安装登录插件需要先安装composer,具体安装方式请自行搜索。 89 | 90 | ## 安装已有的插件 91 | 92 | 目前已有微信([leo108/php_cas_server_oauth_wechat](https://github.com/leo108/php_cas_server_oauth_wechat))和微博([leo108/php_cas_server_oauth_weibo](https://github.com/leo108/php_cas_server_oauth_weibo))两个插件,以微信插件为例: 93 | 94 | 1. 引入插件:`composer require leo108/php_cas_server_oauth_wechat` 95 | 2. 注册插件:修改`config/app.php`文件,在`providers`这一项的末尾加上`Leo108\CASServer\OAuth\WeChat\CASOAuthWeChatServiceProvider::class` 96 | 3. 注册微信操作类:在`app/Providers/AppServiceProvider.php`的`namespace App\Providers;`下方加入`use EasyWeChat\Foundation\Application`;在`register`方法中加入 97 | 98 | ``` 99 | //注册微信公众平台登录方式 100 | $this->app->bind( 101 | 'cas.server.wechat.mp', 102 | function () { 103 | return new Application( 104 | [ 105 | 'app_id' => '公众平台AppId', 106 | 'secret' => '公众平台AppSecret', 107 | 'oauth' => [ 108 | 'scopes' => ['snsapi_userinfo'], 109 | ], 110 | ] 111 | ); 112 | } 113 | ); 114 | //注册微信开放平台登录方式 115 | $this->app->bind( 116 | 'cas.server.wechat.open', 117 | function () { 118 | return new Application( 119 | [ 120 | 'app_id' => '开放平台AppId', 121 | 'secret' => '开放平台AppSecret', 122 | 'oauth' => [ 123 | 'scopes' => ['snsapi_login'], 124 | ], 125 | ] 126 | ); 127 | } 128 | ); 129 | ``` 130 | 可以只注册公众平台或开放平台中的一个,具体参数请参考[EasyWechat文档](https://easywechat.org/zh-cn/docs/) 131 | 4. 导出数据库变更文件:`php artisan vendor:publish --provider="Leo108\CASServer\OAuth\WeChat\CASOAuthWeChatServiceProvider" --tag="migrations"` 132 | 5. 执行数据库变更: `php artisan migrate` 133 | 134 | ## 自定义插件 135 | 136 | 创建自定义插件通常需要如下几个步骤 137 | 138 | 1. 创建数据库结构变更文件:集成第三方登录时,需要记录用户在第三方平台上的id,这样才能将第三方的用户和本站用户关联起来。因此需要一个数据库结构变更文件在`user_oauth`表中创建一个对应的字段来储存这个id。 139 | 2. 创建插件类:插件类需要继承`Leo108\CASServer\OAuth\Plugin`类,并实现`gotoAuthUrl`和`getOAuthUser`。当有用户希望通过此插件提供的登录方式来登录时,CAS系统会调用`gotoAuthUrl`方法来获取第三方登录的授权url并跳转过去;当用户在第三方授权完毕跳转回CAS时,系统会调用`getOAuthUser`来获取授权用户信息,这个方法必须返回一个`Leo108\CASServer\OAuth\OAuthUser`对象。 140 | 3. 将插件注册到插件中心(PluginCenter):这个步骤可以直接在`App\Providers\AppServiceProvider`中实现,也可以单独写一个ServiceProvider然后在`config/app.php`中注册。 141 | 142 | 具体插件的写法,可以参照已有的插件代码。 143 | -------------------------------------------------------------------------------- /resources/assets/js/admin/admin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/9/20. 3 | */ 4 | 5 | require('metismenu'); 6 | 7 | $(function () { 8 | $('#side-menu').metisMenu(); 9 | $(window).bind("load resize", function () { 10 | let topOffset = 50; 11 | let width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 12 | if (width < 768) { 13 | $('div.navbar-collapse').addClass('collapse'); 14 | topOffset = 100; // 2-row-menu 15 | } else { 16 | $('div.navbar-collapse').removeClass('collapse'); 17 | } 18 | 19 | let height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 20 | height = height - topOffset; 21 | if (height < 1) height = 1; 22 | if (height > topOffset) { 23 | $("#page-wrapper").css("min-height", (height) + "px"); 24 | } 25 | }); 26 | let url = window.location; 27 | let element = $('ul.nav a').filter(function () { 28 | return this.href == url || url.href.indexOf(this.href) == 0; 29 | }).addClass('active').parent().parent().addClass('in').parent(); 30 | if (element.is('li')) { 31 | element.addClass('active'); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /resources/assets/js/admin/service/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 2016/9/23. 3 | */ 4 | 5 | Vue.component('admin-service-index', { 6 | data() { 7 | return { 8 | query: { 9 | search: '' 10 | }, 11 | services: [], 12 | editService: { 13 | id: 0, 14 | name: '', 15 | enabled: true, 16 | allow_proxy: false, 17 | hosts: '', 18 | }, 19 | busy: false, 20 | isEdit: false, 21 | } 22 | }, 23 | ready() { 24 | this.services = Laravel.data.services.data; 25 | this.query = Laravel.data.query; 26 | }, 27 | methods: { 28 | bool2icon(value) { 29 | let cls = value ? 'fa-check' : 'fa-times'; 30 | return ''; 31 | }, 32 | displayHosts(hostArr, glu = '
') { 33 | let arr = []; 34 | for (let x in hostArr) { 35 | arr.push(hostArr[x].host); 36 | } 37 | return arr.join(glu); 38 | }, 39 | edit(item) { 40 | this.isEdit = true; 41 | this.editService.id = item.id; 42 | this.editService.name = item.name; 43 | this.editService.enabled = item.enabled; 44 | this.editService.allow_proxy = item.allow_proxy; 45 | this.editService.hosts = this.displayHosts(item.hosts, "\n"); 46 | $('#edit-dialog').modal(); 47 | }, 48 | showAdd() { 49 | this.isEdit = false; 50 | this.editService.id = 0; 51 | this.editService.name = ''; 52 | this.editService.hosts = ''; 53 | this.editService.enabled = true; 54 | this.editService.allow_proxy = false; 55 | $('#edit-dialog').modal(); 56 | }, 57 | save() { 58 | if (this.isEdit) { 59 | this.update(); 60 | } else { 61 | this.store(); 62 | } 63 | }, 64 | store(){ 65 | this.busy = true; 66 | this.$http.post(Laravel.router('service.store'), this.editService) 67 | .then(response => { 68 | this.busy = false; 69 | alert(response.data.msg); 70 | location.reload(); 71 | }) 72 | }, 73 | update(){ 74 | this.busy = true; 75 | this.$http.put(Laravel.router('service.update', {service: this.editService.id}), this.editService) 76 | .then(response => { 77 | this.busy = false; 78 | alert(response.data.msg); 79 | location.reload(); 80 | }) 81 | }, 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /resources/assets/js/admin/user/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/9/20. 3 | */ 4 | 5 | let _ = require('lodash'); 6 | 7 | Vue.component('admin-user-index', { 8 | data() { 9 | return { 10 | query: { 11 | enabled: '', 12 | search: '' 13 | }, 14 | users: [], 15 | editUser: { 16 | id: 0, 17 | name: '', 18 | real_name: '', 19 | email: '', 20 | password: '', 21 | enabled: true, 22 | admin: false 23 | }, 24 | oauthes: null, 25 | busy: false, 26 | isEdit: false, 27 | } 28 | }, 29 | ready() { 30 | this.users = Laravel.data.users.data; 31 | this.query = Laravel.data.query; 32 | }, 33 | methods: { 34 | bool2icon(value) { 35 | let cls = value ? 'fa-check' : 'fa-times'; 36 | return ''; 37 | }, 38 | view_oauth(item) { 39 | this.oauthes = item.oauth.plugins; 40 | $('#oauth-dialog').modal(); 41 | }, 42 | edit(item) { 43 | this.isEdit = true; 44 | this.editUser.id = item.id; 45 | this.editUser.name = item.name; 46 | this.editUser.real_name = item.real_name; 47 | this.editUser.email = item.email; 48 | this.editUser.password = ''; 49 | this.editUser.enabled = item.enabled; 50 | this.editUser.admin = item.admin; 51 | $('#edit-dialog').modal(); 52 | }, 53 | save() { 54 | if (this.isEdit) { 55 | this.update(); 56 | } else { 57 | this.store(); 58 | } 59 | }, 60 | store() { 61 | this.busy = true; 62 | this.$http.post(Laravel.router('user.store'), this.editUser) 63 | .then(response => { 64 | this.busy = false; 65 | alert(response.data.msg); 66 | location.reload(); 67 | }) 68 | }, 69 | update() { 70 | this.busy = true; 71 | this.$http.put(Laravel.router('user.update', {user: this.editUser.id}), this.editUser) 72 | .then(response => { 73 | this.busy = false; 74 | alert(response.data.msg); 75 | location.reload(); 76 | }) 77 | }, 78 | showAdd() { 79 | this.isEdit = false; 80 | this.editUser.id = 0; 81 | this.editUser.name = ''; 82 | this.editUser.real_name = ''; 83 | this.editUser.email = ''; 84 | this.editUser.password = ''; 85 | this.editUser.enabled = true; 86 | this.editUser.admin = false; 87 | $('#edit-dialog').modal(); 88 | }, 89 | isEmpty(value) { 90 | return _.isEmpty(value); 91 | } 92 | } 93 | }); -------------------------------------------------------------------------------- /resources/assets/js/common/backend-router-generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/9/2. 3 | */ 4 | module.exports = (route, param) => { 5 | let routeUrl = Laravel.routes; 6 | let pathArr = route.split('.'); 7 | for (let x in pathArr) { 8 | let path = pathArr[x]; 9 | if (!routeUrl[path]) { 10 | return false; 11 | } 12 | routeUrl = routeUrl[path]; 13 | } 14 | 15 | let append = []; 16 | 17 | for (let x in param) { 18 | let search = '{' + x + '}'; 19 | 20 | if (routeUrl.indexOf(search) >= 0) { 21 | routeUrl = routeUrl.replace('{' + x + '}', param[x]); 22 | } else { 23 | append.push(x + '=' + param[x]); 24 | } 25 | } 26 | 27 | let url = '/' + _.trimStart(routeUrl, '/'); 28 | 29 | if (append.length == 0) { 30 | return url; 31 | } 32 | 33 | if (url.indexOf('?') >= 0) { 34 | url += '&'; 35 | } else { 36 | url += '?'; 37 | } 38 | 39 | url += append.join('&'); 40 | 41 | return url; 42 | }; 43 | -------------------------------------------------------------------------------- /resources/assets/js/common/common.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | window.$ = window.jQuery = require('jquery'); 4 | 5 | window.Laravel = $.extend({ 6 | routes: {}, 7 | lang: {}, 8 | csrfToken: '' 9 | }, window.Laravel ? window.Laravel : {}); 10 | 11 | if (!window.Laravel.csrfToken) { 12 | window.Laravel.csrfToken = $('meta[name=csrf-token]').attr('content'); 13 | } 14 | 15 | require('bootstrap-sass'); 16 | 17 | window.Vue = require('vue'); 18 | require('vue-resource'); 19 | 20 | Vue.http.interceptors.push((request, next) => { 21 | request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken); 22 | next(); 23 | }); 24 | 25 | Laravel.router = require('./backend-router-generator'); 26 | Laravel.trans = require('./translator'); 27 | window.bootbox = require('bootbox'); 28 | 29 | require('./jquery-ajax'); 30 | require('./errors'); 31 | -------------------------------------------------------------------------------- /resources/assets/js/common/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Laravel form error collection class. 3 | */ 4 | window.LaravelFormErrors = function () { 5 | this.errors = {}; 6 | 7 | /** 8 | * Determine if the collection has any errors. 9 | */ 10 | this.hasErrors = function () { 11 | return ! _.isEmpty(this.errors); 12 | }; 13 | 14 | 15 | /** 16 | * Determine if the collection has errors for a given field. 17 | */ 18 | this.has = function (field) { 19 | return _.indexOf(_.keys(this.errors), field) > -1; 20 | }; 21 | 22 | 23 | /** 24 | * Get all of the raw errors for the collection. 25 | */ 26 | this.all = function () { 27 | return this.errors; 28 | }; 29 | 30 | 31 | /** 32 | * Get all of the errors for the collection in a flat array. 33 | */ 34 | this.flatten = function () { 35 | return _.flatten(_.toArray(this.errors)); 36 | }; 37 | 38 | 39 | /** 40 | * Get the first error message for a given field. 41 | */ 42 | this.get = function (field) { 43 | if (this.has(field)) { 44 | return this.errors[field][0]; 45 | } 46 | }; 47 | 48 | 49 | /** 50 | * Set the raw errors for the collection. 51 | */ 52 | this.set = function (errors) { 53 | if (typeof errors === 'object') { 54 | this.errors = errors; 55 | } else { 56 | this.errors = {'form': ['Something went wrong. Please try again or contact customer support.']}; 57 | } 58 | }; 59 | 60 | 61 | /** 62 | * Forget all of the errors currently in the collection. 63 | */ 64 | this.forget = function () { 65 | this.errors = {}; 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /resources/assets/js/common/jquery-ajax.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/8/29. 3 | */ 4 | 5 | (function (factory) { 6 | factory(jQuery); 7 | }(function ($, undefined) { 8 | $.each(["post", "get", "put", "delete"], function (i, method) { 9 | $[method] = function (url, data, callback, type) { 10 | 11 | // Shift arguments if data argument was omitted 12 | if ($.isFunction(data)) { 13 | type = type || callback; 14 | callback = data; 15 | data = undefined; 16 | } 17 | 18 | // The url can be an options object (which then must have .url) 19 | return $.ajax($.extend({ 20 | url: url, 21 | type: method, 22 | dataType: type, 23 | data: data, 24 | success: callback, 25 | headers: { 26 | 'X-CSRF-TOKEN': Laravel.csrfToken 27 | } 28 | }, $.isPlainObject(url) && url)); 29 | }; 30 | }); 31 | })); 32 | -------------------------------------------------------------------------------- /resources/assets/js/common/translator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/9/20. 3 | */ 4 | 5 | module.exports = (name) => { 6 | if (Laravel.lang[name]) { 7 | return Laravel.lang[name]; 8 | } 9 | return name; 10 | }; -------------------------------------------------------------------------------- /resources/assets/js/front/home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by leo108 on 16/9/20. 3 | */ 4 | $(document).ready(function () { 5 | var $pwdDialog = $('#change-pwd-dialog'); 6 | $('#btn_logout').click(function () { 7 | bootbox.confirm(Laravel.trans('message.confirm_logout'), function (ret) { 8 | if (ret) { 9 | location.href = Laravel.router('logout'); 10 | } 11 | }); 12 | }); 13 | 14 | $('#btn_change_pwd').click(function () { 15 | $pwdDialog.modal(); 16 | }); 17 | 18 | $('#btn-save-pwd').click(function () { 19 | $pwdDialog.find('div.form-group').removeClass('has-error'); 20 | var map = { 21 | 'old': 'old-pwd', 22 | 'new1': 'new-pwd', 23 | 'new2': 'new-pwd2' 24 | }; 25 | var val = {}; 26 | 27 | for (var x in map) { 28 | var $input = $('#' + map[x]); 29 | val[x] = $input.val(); 30 | if ($input.val() == '') { 31 | $input.closest('div.form-group').addClass('has-error'); 32 | } 33 | } 34 | 35 | if (val['new1'] != val['new2']) { 36 | $('#new-pwd2').closest('div.form-group').addClass('has-error'); 37 | } 38 | 39 | if ($pwdDialog.find('.has-error').length > 0) { 40 | return; 41 | } 42 | 43 | var req = { 44 | 'old': val['old'], 45 | 'new': val['new1'], 46 | }; 47 | 48 | $.post(Laravel.router('change_pwd'), req, function (ret) { 49 | alert(ret.msg); 50 | $pwdDialog.modal('hide'); 51 | }, 'json'); 52 | }); 53 | }); -------------------------------------------------------------------------------- /resources/assets/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | -------------------------------------------------------------------------------- /resources/assets/less/variables.less: -------------------------------------------------------------------------------- 1 | // Variables 2 | 3 | @gray-darker: lighten(#000, 13.5%); 4 | @gray-dark: lighten(#000, 20%); 5 | @gray: lighten(#000, 33.5%); 6 | @gray-light: lighten(#000, 60%); 7 | @gray-lighter: lighten(#000, 93.5%); 8 | @gray-lightest: lighten(#000, 97.25%); 9 | @brand-primary: #428bca; 10 | @brand-success: #5cb85c; 11 | @brand-info: #5bc0de; 12 | @brand-warning: #f0ad4e; 13 | @brand-danger: #d9534f; 14 | 15 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/bootstrap-sass/assets/stylesheets/_bootstrap.scss"; 2 | 3 | @import "node_modules/font-awesome/scss/font-awesome.scss"; -------------------------------------------------------------------------------- /resources/lang/cn/admin.php: -------------------------------------------------------------------------------- 1 | '系统管理', 4 | 'back_to_front' => '返回前台', 5 | 'menu' => [ 6 | 'dashboard' => '仪表盘', 7 | 'users' => '用户', 8 | 'services' => '服务', 9 | ], 10 | 'dashboard' => [ 11 | 'view_details' => '查看详情', 12 | 'user_total' => '用户数', 13 | 'service_total' => '服务数', 14 | 'service_enabled' => '启用数', 15 | 'user_active' => '启用数', 16 | 'user_admin' => '管理员数', 17 | ], 18 | 'user' => [ 19 | 'username' => '用户名', 20 | 'password' => '密码', 21 | 'email' => '邮箱', 22 | 'real_name' => '真实姓名', 23 | 'enabled' => '是否启用', 24 | 'admin' => '是否管理员', 25 | 'created_at' => '创建时间', 26 | 'updated_at' => '更新时间', 27 | 'add' => '添加用户', 28 | 'add_or_edit' => '添加/编辑用户', 29 | 'add_ok' => '添加用户成功', 30 | 'edit_ok' => '编辑用户成功', 31 | 'enabled_all' => '全部', 32 | 'enabled_yes' => '启用', 33 | 'enabled_no' => '禁用', 34 | 'view_oauth' => '第三方绑定', 35 | 'oauth' => [ 36 | 'name' => '平台名称', 37 | 'status' => '绑定状态', 38 | ], 39 | ], 40 | 'service' => [ 41 | 'add_or_edit' => '添加/编辑服务', 42 | 'name' => '服务名称', 43 | 'hosts' => '域名', 44 | 'enabled' => '是否启用', 45 | 'allow_proxy' => '启用代理', 46 | 'created_at' => '创建时间', 47 | 'add' => '添加服务', 48 | 'hosts_placeholder' => "一行一个,如\nleo108.com\ngithub.com", 49 | 'add_ok' => '添加服务成功', 50 | 'edit_ok' => '编辑服务成功', 51 | ], 52 | 'operation' => '操作', 53 | 'edit' => '编辑', 54 | 'search' => '搜索', 55 | 'total' => '总计: ', 56 | 'filter' => '筛选', 57 | ]; 58 | -------------------------------------------------------------------------------- /resources/lang/cn/auth.php: -------------------------------------------------------------------------------- 1 | '账号密码不匹配', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 'username' => '用户名', 19 | 'password' => '密码', 20 | 'remember_me' => '保持登录', 21 | 'logout' => '注销', 22 | 'logged_in_as' => '你已登录为 :name', 23 | 'change_pwd' => '修改密码', 24 | 'old_pwd' => '旧密码', 25 | 'new_pwd' => '新密码', 26 | 'new_pwd2' => '重复密码', 27 | 'need_login' => '请先登录', 28 | 'register' => '注册', 29 | 'email' => '邮箱', 30 | 'login' => '登录', 31 | 'forgot_pwd' => '忘记密码', 32 | 'real_name' => '真实姓名', 33 | 'confirm_pwd' => '重复密码', 34 | 'logged_out' => '注销成功', 35 | 'oauth_bound' => '绑定第三方登录成功', 36 | ]; 37 | -------------------------------------------------------------------------------- /resources/lang/cn/common.php: -------------------------------------------------------------------------------- 1 | '提交', 4 | 'ok' => '确认', 5 | 'abort' => '放弃', 6 | 'cancel' => '取消', 7 | 'confirm' => '确认', 8 | 'close' => '关闭', 9 | 'yes' => '是', 10 | 'no' => '否', 11 | ]; -------------------------------------------------------------------------------- /resources/lang/cn/message.php: -------------------------------------------------------------------------------- 1 | '确认要注销登录?', 4 | 'cas_redirect_warn' => '即将跳转到 :url', 5 | 'invalid_old_pwd' => '旧密码不正确', 6 | 'change_pwd_ok' => '密码修改成功', 7 | 'user' => [ 8 | 'not_exists' => '该用户不存在', 9 | 'name_duplicated' => '该用户名已被占用', 10 | 'email_duplicated' => '该邮箱地址已被占用', 11 | ], 12 | 'service' => [ 13 | 'name_duplicated' => '该服务名已被占用', 14 | 'host_occupied' => '域名 :host 已被其他服务占用', 15 | ], 16 | 'back_to_home' => '返回首页', 17 | ]; 18 | -------------------------------------------------------------------------------- /resources/lang/cn/pagination.php: -------------------------------------------------------------------------------- 1 | '« 上一页', 17 | 'next' => '下一页 »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/cn/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => '密码修改成功', 18 | 'sent' => '密码重置邮件已经发送到你的邮箱', 19 | 'token' => '重置链接无效', 20 | 'user' => "该邮箱尚未注册", 21 | 'forget_pwd' => '忘记密码', 22 | 'reset_pwd' => '重置密码', 23 | 'email' => '邮箱地址', 24 | 'email_subject' => '密码重置链接', 25 | 'email_content' => '点击链接重置密码', 26 | ]; 27 | -------------------------------------------------------------------------------- /resources/lang/cn/validation.php: -------------------------------------------------------------------------------- 1 | 'The :attribute must be accepted.', 17 | 'active_url' => ':attribute 不是一个合法的网址', 18 | 'after' => ':attribute 必须晚于 :date.', 19 | 'alpha' => ':attribute 只允许包含字母', 20 | 'alpha_dash' => ':attribute 只允许包含字母、数字、-和_', 21 | 'alpha_num' => ':attribute 只允许包含字母和数字', 22 | 'array' => ':attribute 必须是一个数组', 23 | 'before' => ':attribute 必须早于 :date.', 24 | 'between' => [ 25 | 'numeric' => 'The :attribute must be between :min and :max.', 26 | 'file' => 'The :attribute must be between :min and :max kilobytes.', 27 | 'string' => 'The :attribute must be between :min and :max characters.', 28 | 'array' => 'The :attribute must have between :min and :max items.', 29 | ], 30 | 'boolean' => ':attribute 必须是 true 或 false.', 31 | 'confirmed' => '新旧 :attribute 不匹配', 32 | 'date' => ':attribute 不是一个合法的日期', 33 | 'date_format' => ':attribute 不符合日期格式 :format.', 34 | 'different' => ':attribute 和 :other 不能一致', 35 | 'digits' => ':attribute 的位数必须是 :digits 位', 36 | 'digits_between' => ':attribute 的位数必须在 :min 和 :max 之间', 37 | 'dimensions' => 'The :attribute has invalid image dimensions.', 38 | 'distinct' => 'The :attribute field has a duplicate value.', 39 | 'email' => ':attribute 必须是一个合法的邮箱地址.', 40 | 'exists' => 'The selected :attribute is invalid.', 41 | 'file' => 'The :attribute must be a file.', 42 | 'filled' => 'The :attribute field is required.', 43 | 'image' => 'The :attribute must be an image.', 44 | 'in' => 'The selected :attribute is invalid.', 45 | 'in_array' => 'The :attribute field does not exist in :other.', 46 | 'integer' => 'The :attribute must be an integer.', 47 | 'ip' => 'The :attribute must be a valid IP address.', 48 | 'json' => 'The :attribute must be a valid JSON string.', 49 | 'max' => [ 50 | 'numeric' => 'The :attribute may not be greater than :max.', 51 | 'file' => 'The :attribute may not be greater than :max kilobytes.', 52 | 'string' => 'The :attribute may not be greater than :max characters.', 53 | 'array' => 'The :attribute may not have more than :max items.', 54 | ], 55 | 'mimes' => 'The :attribute must be a file of type: :values.', 56 | 'min' => [ 57 | 'numeric' => ':attribute 必须大于 :min.', 58 | 'file' => 'The :attribute must be at least :min kilobytes.', 59 | 'string' => ':attribute 至少需要 :min 个字符', 60 | 'array' => 'The :attribute must have at least :min items.', 61 | ], 62 | 'not_in' => 'The selected :attribute is invalid.', 63 | 'numeric' => 'The :attribute must be a number.', 64 | 'present' => 'The :attribute field must be present.', 65 | 'regex' => 'The :attribute format is invalid.', 66 | 'required' => '请填写 :attribute', 67 | 'required_if' => 'The :attribute field is required when :other is :value.', 68 | 'required_unless' => 'The :attribute field is required unless :other is in :values.', 69 | 'required_with' => 'The :attribute field is required when :values is present.', 70 | 'required_with_all' => 'The :attribute field is required when :values is present.', 71 | 'required_without' => 'The :attribute field is required when :values is not present.', 72 | 'required_without_all' => 'The :attribute field is required when none of :values are present.', 73 | 'same' => 'The :attribute and :other must match.', 74 | 'size' => [ 75 | 'numeric' => 'The :attribute must be :size.', 76 | 'file' => 'The :attribute must be :size kilobytes.', 77 | 'string' => 'The :attribute must be :size characters.', 78 | 'array' => 'The :attribute must contain :size items.', 79 | ], 80 | 'string' => 'The :attribute must be a string.', 81 | 'timezone' => 'The :attribute must be a valid zone.', 82 | 'unique' => 'The :attribute has already been taken.', 83 | 'url' => 'The :attribute format is invalid.', 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Custom Validation Language Lines 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may specify custom validation messages for attributes using the 91 | | convention "attribute.rule" to name the lines. This makes it quick to 92 | | specify a specific custom language line for a given attribute rule. 93 | | 94 | */ 95 | 96 | 'custom' => [ 97 | 'attribute-name' => [ 98 | 'rule-name' => 'custom-message', 99 | ], 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Custom Validation Attributes 105 | |-------------------------------------------------------------------------- 106 | | 107 | | The following language lines are used to swap attribute place-holders 108 | | with something more reader friendly such as E-Mail Address instead 109 | | of "email". This simply helps us make messages a little cleaner. 110 | | 111 | */ 112 | 113 | 'attributes' => [], 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /resources/lang/en/admin.php: -------------------------------------------------------------------------------- 1 | 'System Manage', 4 | 'back_to_front' => 'Back To Front End', 5 | 'menu' => [ 6 | 'dashboard' => 'Dashboard', 7 | 'users' => 'Users', 8 | 'services' => 'Services', 9 | ], 10 | 'dashboard' => [ 11 | 'view_details' => 'View Details', 12 | 'user_total' => 'Users', 13 | 'service_total' => 'Service', 14 | 'service_enabled' => 'Enabled', 15 | 'user_active' => 'Active', 16 | 'user_admin' => 'Admin', 17 | ], 18 | 'user' => [ 19 | 'username' => 'User Name', 20 | 'password' => 'Password', 21 | 'email' => 'Email', 22 | 'real_name' => 'Real Name', 23 | 'enabled' => 'Enabled', 24 | 'admin' => 'Admin', 25 | 'created_at' => 'Created Time', 26 | 'updated_at' => 'Updated Time', 27 | 'add' => 'Add User', 28 | 'add_or_edit' => 'Add/Edit User', 29 | 'add_ok' => 'Add user successful', 30 | 'edit_ok' => 'Edit user successful', 31 | 'enabled_all' => 'All', 32 | 'enabled_yes' => 'Enabled', 33 | 'enabled_no' => 'Disabled', 34 | 'view_oauth' => 'OAuth Binding', 35 | 'oauth' => [ 36 | 'name' => 'Name', 37 | 'status' => 'Binding Status', 38 | ], 39 | ], 40 | 'service' => [ 41 | 'add_or_edit' => 'Add/Edit Service', 42 | 'name' => 'Service Name', 43 | 'hosts' => 'Service Domain', 44 | 'enabled' => 'Enabled', 45 | 'allow_proxy' => 'Allow proxy', 46 | 'created_at' => 'Created Time', 47 | 'add' => 'Add Service', 48 | 'hosts_placeholder' => "One domain per line, eg:\nleo108.com\ngithub.com", 49 | 'add_ok' => 'Add service successful', 50 | 'edit_ok' => 'Edit service successful', 51 | ], 52 | 'operation' => 'Operation', 53 | 'edit' => 'Edit', 54 | 'search' => 'Search', 55 | 'total' => 'Total: ', 56 | 'filter' => 'Filter', 57 | ]; 58 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 'username' => 'User Name', 19 | 'password' => 'Password', 20 | 'remember_me' => 'Remember Me', 21 | 'logout' => 'Logout', 22 | 'logged_in_as' => 'You are logged in as :name', 23 | 'change_pwd' => 'Change Password', 24 | 'old_pwd' => 'Old Password', 25 | 'new_pwd' => 'New Password', 26 | 'new_pwd2' => 'Confirm New Password', 27 | 'need_login' => 'You need to login first', 28 | 'register' => 'Register', 29 | 'email' => 'E-Mail Address', 30 | 'login' => 'Login', 31 | 'forgot_pwd' => 'Forgot Your Password?', 32 | 'real_name' => 'Real Name', 33 | 'confirm_pwd' => 'Confirm Password', 34 | 'logged_out' => 'Logout successful', 35 | 'oauth_bound' => 'You have bound oauth successfully', 36 | ]; 37 | -------------------------------------------------------------------------------- /resources/lang/en/common.php: -------------------------------------------------------------------------------- 1 | 'Submit', 4 | 'ok' => 'OK', 5 | 'abort' => 'Abort', 6 | 'cancel' => 'Cancel', 7 | 'confirm' => 'Confirm', 8 | 'close' => 'Close', 9 | 'yes' => 'Yes', 10 | 'no' => 'No', 11 | ]; -------------------------------------------------------------------------------- /resources/lang/en/message.php: -------------------------------------------------------------------------------- 1 | 'Confirm to logout ?', 4 | 'cas_redirect_warn' => 'Redirect to :url', 5 | 'invalid_old_pwd' => 'Your old password is invalid', 6 | 'change_pwd_ok' => 'Your password has been changed', 7 | 'user' => [ 8 | 'not_exists' => 'User not exists', 9 | 'name_duplicated' => 'Username duplicated', 10 | 'email_duplicated' => 'Email duplicated', 11 | ], 12 | 'service' => [ 13 | 'name_duplicated' => 'Service name duplicated', 14 | 'host_occupied' => 'Service host :host is occupied', 15 | ], 16 | 'back_to_home' => 'Back To Home', 17 | ]; 18 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | 'The :attribute must be accepted.', 17 | 'active_url' => 'The :attribute is not a valid URL.', 18 | 'after' => 'The :attribute must be a date after :date.', 19 | 'alpha' => 'The :attribute may only contain letters.', 20 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', 21 | 'alpha_num' => 'The :attribute may only contain letters and numbers.', 22 | 'array' => 'The :attribute must be an array.', 23 | 'before' => 'The :attribute must be a date before :date.', 24 | 'between' => [ 25 | 'numeric' => 'The :attribute must be between :min and :max.', 26 | 'file' => 'The :attribute must be between :min and :max kilobytes.', 27 | 'string' => 'The :attribute must be between :min and :max characters.', 28 | 'array' => 'The :attribute must have between :min and :max items.', 29 | ], 30 | 'boolean' => 'The :attribute field must be true or false.', 31 | 'confirmed' => 'The :attribute confirmation does not match.', 32 | 'date' => 'The :attribute is not a valid date.', 33 | 'date_format' => 'The :attribute does not match the format :format.', 34 | 'different' => 'The :attribute and :other must be different.', 35 | 'digits' => 'The :attribute must be :digits digits.', 36 | 'digits_between' => 'The :attribute must be between :min and :max digits.', 37 | 'dimensions' => 'The :attribute has invalid image dimensions.', 38 | 'distinct' => 'The :attribute field has a duplicate value.', 39 | 'email' => 'The :attribute must be a valid email address.', 40 | 'exists' => 'The selected :attribute is invalid.', 41 | 'file' => 'The :attribute must be a file.', 42 | 'filled' => 'The :attribute field is required.', 43 | 'image' => 'The :attribute must be an image.', 44 | 'in' => 'The selected :attribute is invalid.', 45 | 'in_array' => 'The :attribute field does not exist in :other.', 46 | 'integer' => 'The :attribute must be an integer.', 47 | 'ip' => 'The :attribute must be a valid IP address.', 48 | 'json' => 'The :attribute must be a valid JSON string.', 49 | 'max' => [ 50 | 'numeric' => 'The :attribute may not be greater than :max.', 51 | 'file' => 'The :attribute may not be greater than :max kilobytes.', 52 | 'string' => 'The :attribute may not be greater than :max characters.', 53 | 'array' => 'The :attribute may not have more than :max items.', 54 | ], 55 | 'mimes' => 'The :attribute must be a file of type: :values.', 56 | 'min' => [ 57 | 'numeric' => 'The :attribute must be at least :min.', 58 | 'file' => 'The :attribute must be at least :min kilobytes.', 59 | 'string' => 'The :attribute must be at least :min characters.', 60 | 'array' => 'The :attribute must have at least :min items.', 61 | ], 62 | 'not_in' => 'The selected :attribute is invalid.', 63 | 'numeric' => 'The :attribute must be a number.', 64 | 'present' => 'The :attribute field must be present.', 65 | 'regex' => 'The :attribute format is invalid.', 66 | 'required' => 'The :attribute field is required.', 67 | 'required_if' => 'The :attribute field is required when :other is :value.', 68 | 'required_unless' => 'The :attribute field is required unless :other is in :values.', 69 | 'required_with' => 'The :attribute field is required when :values is present.', 70 | 'required_with_all' => 'The :attribute field is required when :values is present.', 71 | 'required_without' => 'The :attribute field is required when :values is not present.', 72 | 'required_without_all' => 'The :attribute field is required when none of :values are present.', 73 | 'same' => 'The :attribute and :other must match.', 74 | 'size' => [ 75 | 'numeric' => 'The :attribute must be :size.', 76 | 'file' => 'The :attribute must be :size kilobytes.', 77 | 'string' => 'The :attribute must be :size characters.', 78 | 'array' => 'The :attribute must contain :size items.', 79 | ], 80 | 'string' => 'The :attribute must be a string.', 81 | 'timezone' => 'The :attribute must be a valid zone.', 82 | 'unique' => 'The :attribute has already been taken.', 83 | 'url' => 'The :attribute format is invalid.', 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Custom Validation Language Lines 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may specify custom validation messages for attributes using the 91 | | convention "attribute.rule" to name the lines. This makes it quick to 92 | | specify a specific custom language line for a given attribute rule. 93 | | 94 | */ 95 | 96 | 'custom' => [ 97 | 'attribute-name' => [ 98 | 'rule-name' => 'custom-message', 99 | ], 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Custom Validation Attributes 105 | |-------------------------------------------------------------------------- 106 | | 107 | | The following language lines are used to swap attribute place-holders 108 | | with something more reader friendly such as E-Mail Address instead 109 | | of "email". This simply helps us make messages a little cleaner. 110 | | 111 | */ 112 | 113 | 'attributes' => [], 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /resources/views/admin/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.admin') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |

@lang('admin.menu.dashboard')

8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
{{ $user['total'] }}
22 |
@lang('admin.dashboard.user_total')
23 |
24 |
25 |
26 |
27 | {{ $user['active'] }} @lang('admin.dashboard.user_active') 28 |
29 |
30 | {{ $user['admin'] }} @lang('admin.dashboard.user_admin') 31 |
32 |
33 |
34 | 35 | 40 | 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
{{ $service['total'] }}
53 |
@lang('admin.dashboard.service_total')
54 |
55 |
56 |
57 |
58 | {{ $service['enabled'] }} @lang('admin.dashboard.service_enabled') 59 |
60 |
61 |
62 | 63 | 68 | 69 |
70 |
71 | 72 |
73 | 74 |
75 | 76 | @endsection 77 | -------------------------------------------------------------------------------- /resources/views/admin/service.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.admin') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |
8 |
9 |
10 |
@lang('admin.filter')
11 |
12 |
13 |
14 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 |
#@lang('admin.service.name')@lang('admin.service.hosts')@lang('admin.service.enabled')@lang('admin.service.allow_proxy')@lang('admin.service.created_at') 36 | 37 |
@{{ item.id }}@{{ item.name }}@{{{ displayHosts(item.hosts) }}}@{{{ bool2icon(item.enabled) }}}@{{{ bool2icon(item.allow_proxy) }}}@{{ item.created_at }} 49 | {{ trans('admin.edit') }} 50 |
54 |
@lang('admin.total') {{ $services->total() }}
55 |
{{ $services->appends($query)->links() }}
56 |
57 |
58 | 59 |
60 | 61 |
62 | 63 | 64 | 113 |
114 | @endsection 115 | 116 | @section('javascript') 117 | 129 | 130 | @endsection -------------------------------------------------------------------------------- /resources/views/auth/emails/password.blade.php: -------------------------------------------------------------------------------- 1 | Click here to reset your password: {{ $link }} 2 | -------------------------------------------------------------------------------- /resources/views/auth/logged_out.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 16 |
17 |
18 |
19 | @endsection 20 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 83 |
84 |
85 |
86 | @endsection 87 | -------------------------------------------------------------------------------- /resources/views/auth/login_warn.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 21 |
22 |
23 |
24 | @endsection 25 | -------------------------------------------------------------------------------- /resources/views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
Reset Password
10 |
11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 |
18 | {{ csrf_field() }} 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @if ($errors->has('email')) 27 | 28 | {{ $errors->first('email') }} 29 | 30 | @endif 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /resources/views/auth/passwords/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Reset Password
9 | 10 |
11 |
12 | {{ csrf_field() }} 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('email')) 23 | 24 | {{ $errors->first('email') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('password')) 37 | 38 | {{ $errors->first('password') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | @if ($errors->has('password_confirmation')) 50 | 51 | {{ $errors->first('password_confirmation') }} 52 | 53 | @endif 54 |
55 |
56 | 57 |
58 |
59 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | @endsection 71 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ config('cas_server.site_name') }}
9 |
10 | @if($oauth) 11 |
@lang('auth.oauth_bound')
12 | @endif 13 |
14 | {{ csrf_field() }} 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('name')) 23 | 24 | {{ $errors->first('name') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('real_name')) 37 | 38 | {{ $errors->first('real_name') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 | 49 | 50 | @if ($errors->has('email')) 51 | 52 | {{ $errors->first('email') }} 53 | 54 | @endif 55 |
56 |
57 | 58 |
59 | 60 | 61 |
62 | 63 | 64 | @if ($errors->has('password')) 65 | 66 | {{ $errors->first('password') }} 67 | 68 | @endif 69 |
70 |
71 | 72 |
73 | 74 | 75 |
76 | 77 | 78 | @if ($errors->has('password_confirmation')) 79 | 80 | {{ $errors->first('password_confirmation') }} 81 | 82 | @endif 83 |
84 |
85 | 86 |
87 |
88 | 91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | @endsection 100 | -------------------------------------------------------------------------------- /resources/views/common/message.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 | {!! $message !!} 9 |
10 | 11 | @if($subMsg) 12 |
13 | {!! $subMsg !!} 14 |
15 | @endif 16 | 17 | @if($btnName) 18 | 21 | @endif 22 |
23 |
24 |
25 | @endsection -------------------------------------------------------------------------------- /resources/views/errors/503.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Be right back. 5 | 6 | 7 | 8 | 39 | 40 | 41 |
42 |
43 |
Be right back.
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /resources/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 27 |
28 |
29 |
30 | 71 | @endsection 72 | 73 | @section('javascript') 74 | @include('vendor.bootbox') 75 | 84 | 85 | @endsection 86 | 87 | -------------------------------------------------------------------------------- /resources/views/layouts/admin.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CAS Server 7 | 8 | 9 | 10 | 11 | 12 | @yield('stylesheet') 13 | 14 | 15 | 16 | 58 | 59 | @yield('content') 60 | 61 | 62 | 63 | @yield('javascript') 64 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CAS Server 7 | 8 | 9 | 10 | 11 | 12 | @yield('stylesheet') 13 | 14 | 15 | 16 | @yield('content') 17 | 18 | @yield('javascript') 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/views/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/vendor/bootbox.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 22 | 23 | return $app; 24 | } 25 | } 26 | --------------------------------------------------------------------------------