middleware('auth'); } /** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { if(Auth::user()->hasRole('admin')) { $domains = Domain::all(); return view('layouts/vmail',['domains' => $domains]); } elseif(Auth::user()->hasRole('user')) { $domains = Auth::user()->cards; return view('layouts/vmail',['domains' => $domains]); } else { return view('layouts/unverified',); } return view('layouts/vmail',['domains' => $domains]); } public function domainsAjax(Request $request) { $action = $request->input('action'); try { // block unverified users if(!(Auth::user()->hasRole('admin') || Auth::user()->hasRole('user'))){ throw PermissionException('Not Authorized'); } if($action == 'create') { if(!(Auth::user()->hasRole('admin'))) { throw PermissionException('Not Authorized');} $validatedData = $request->validate([ 'domain' => ['required', 'string', 'unique:App\Domain,domain'], ]); $domain = new Domain(); $domain->domain = $validatedData['domain']; $domain->save(); $domain->users()->attach(Auth::user()->id,['role' => 'own']); return [$domain]; } elseif($action == 'rename') { if(!(Auth::user()->hasRole('admin'))) { throw PermissionException('Not Authorized');} $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], 'newname' => ['required', 'string', 'unique:App\Domain,domain'], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); $accounts = $domain->accounts; $aliases = $domain->aliases; $users = $domain->users; foreach($users as $u) { $domain->users()->detach($u->id); } $domain->domain = $validatedData['newname']; $domain->save(); foreach($accounts as $a) { $a->domain()->associate($domain); $a->save(); } foreach($aliases as $a) { $a->source_domain()->associate($domain); $a->save(); } foreach($users as $u) { $domain->users()->attach($u->id); } $domain->save(); return [$domain]; } elseif($action == 'drop') { if(!(Auth::user()->hasRole('admin'))) { throw PermissionException('Not Authorized');} $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); // remove all aliases and accounts related to this domain $domain->delete(); foreach(Account::where('domain',$validatedData['domain'])->get() as $a){ $a->delete(); } foreach(Alias::where('source_domain',$validatedData['domain'])->get() as $a){ $a->delete(); } return true; } elseif($action == 'listadmins') { $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); if(!Auth::user()->hasRole('admin')){ // if not an admin, check if edit roles are enabled for this domain $domain->needRole('edit'); // throws exception if curren user does not have this role } return $domain->users; } elseif($action == 'setadmin') { $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], 'userid' => ['required', 'int', 'exists:App\User,id'], 'role' => ['required', 'string','in:own,edit,view'], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); if(!Auth::user()->hasRole('admin')){ // if not an admin, check if edit roles are enabled for this domain $domain->needRole('own'); // throws exception if curren user does not have this role if($validatedData['userid'] == Auth::user()->id) { throw ValidationException('Cannot edit own ownership'); } } $domain->users()->attach($validatedData['userid'],['role' => $validatedData['userid']]); $domain->save(); return true; } elseif($action == 'deladmin') { $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], 'userid' => ['required', 'int', 'exists:App\User,id'], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); if(!Auth::user()->hasRole('admin')){ // if not an admin, check if edit roles are enabled for this domain $domain->needRole('own'); // throws exception if curren user does not have this role if($validatedData['userid'] == Auth::user()->id) { throw ValidationException('Cannot remove own ownership'); } } $domain->users()->detach($validatedData['userid']); $domain->save(); return true; } elseif($action == 'listusers') { $users = User::all(); return $users; } elseif($action == 'list') { if(Auth::user()->hasRole('admin')) { $domains = Domain::all(); } else { $domains = Auth::user()->cards; } return $domains; } else { return response(['fail' => 'action', 'errors' => ['Action unknown: '. $action]],400); } } catch(ValidationException $v) { return response(['fail' => 'validation', 'errors' => $v->errors()],400); } catch(PermissionException $x) { return response(['fail' => 'role', 'errors' => ['Action requires role '. $x->role()]],403); } catch(ErrorException $v) { return response(['fail' => 'errors', 'errors' => $v->errors()],400); } } public function tlsAjax(Request $request) { $action = $request->input('action'); try { // allow only admin if(!(Auth::user()->hasRole('admin'))) { throw PermissionException('Not Authorized');} if($action == 'list') { $policies = TlsPolicy::all(); return $policies; } elseif($action == 'getpolicy') { $validatedData = $request->validate([ 'domain' => ['required', 'string',], ]); $domain = Domain::where('domain',$validatedData['domain'])->first(); $tlspolicy = $domain->tlspolicy; if(empty($tlspolicy)) { return ['policy' => 'null', 'params' => '']; } else { return $tlspolicy; } return response(['fail' => 'action', 'errors' => ["Unauthorized for domain '{$validatedData['domain']}'"]],403); } elseif($action == 'setpolicy') { $validatedData = $request->validate([ 'domain' => ['required', 'string', ], 'policy' => ['required', 'string','in:null,none,may,encrypt,dane,dane-only,fingerprint,verify,secure'], 'params' => ['nullable','string'], ]); $tlspolicy = TlsPolicy::where('domain',$validatedData['domain'])->first(); if($tlspolicy == null){ $tlspolicy = new TlsPolicy(['domain' => $validatedData['domain'], 'policy' => $validatedData['policy'], 'params' => $validatedData['params'] ]); $tlspolicy->save(); } else { $tlspolicy->policy = $validatedData['policy']; $tlspolicy->params = $validatedData['params']; $tlspolicy->save(); } return $tlspolicy; } elseif($action == 'delpolicy') { $validatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\TlsPolicy,domain' ], ]); $tlspolicy = TlsPolicy::where('domain',$validatedData['domain'])->first(); $tlspolicy->delete(); return true; } else { return response(['fail' => 'action', 'errors' => ['Action unknown: '. $action]],400); } } catch(ValidationException $v) { return response(['fail' => 'validation', 'errors' => $v->errors()],400); } catch(PermissionException $x) { return response(['fail' => 'role', 'errors' => ['Action requires role '. $x->role()]],403); } catch(ErrorException $v) { return response(['fail' => 'errors', 'errors' => $v->errors()],400); } } public function accountsAjax(Request $request) { $action = $request->input('action'); try { // block unverified users if(!(Auth::user()->hasRole('admin') || Auth::user()->hasRole('user'))){ throw PermissionException('Not Authorized'); } $firstValidatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], ]); $domain = Domain::where('domain',$firstValidatedData['domain'])->first(); if(!Auth::user()->hasRole('admin')){ // if not an admin, check if edit roles are enabled for this domain $domain->needRole('edit'); // throws exception if curren user does not have this role } if($action == 'create') { $validatedData = $request->validate([ 'username' => ['required', 'string',], 'enabled' => ['required', 'boolean',], 'sendonly' => ['required', 'boolean',], 'quota' => ['required', 'integer',], 'password' => ['required', 'string',], ]); if(Account::where('username',$validatedData['username'])->where('domain', $firstValidatedData['domain'])->count() > 0){ throw new ValidationException('Account already exists'); } // encode password if(Entropy::Calculate($validatedData['password']) < static::MinimumEntropy) { throw new ValidationException('Password is not complex enough'); } $hash = DovecotPw::Encrypt($validatedData['password']); $account = Account::Create([ 'username' => $validatedData['username'], 'domain' => $firstValidatedData['domain'], 'enabled' => $validatedData['enabled'], 'sendonly' => $validatedData['sendonly'], 'quota' => $validatedData['quota'], 'password' => $hash, ]); $account->save(); return [$account]; } elseif($action == 'checkusername') { $validatedData = $request->validate([ 'username' => ['required', 'string', ''], 'id' => ['nullable','integer'], ]); if(!empty($validatedData['id'])) { $a = Account::Find($validatedData['id']); if($validatedData['username'] == $a->username) { return true; } } return (Account::where('domain',$domain->domain)->where('username',$validatedData['username'])->count() == 0); } elseif($action == 'list') { $accounts = $domain->accounts; return $accounts; } elseif($action == 'update') { $validatedData = $request->validate([ 'id' => ['required','integer'], 'username' => ['nullable', 'string',], 'enabled' => ['required', 'boolean',], 'sendonly' => ['required', 'boolean',], 'quota' => ['required', 'integer',], 'password' => ['nullable', 'string',], ]); $account = Account::Find($validatedData['id']); if(!empty($validatedData['username']) && $validatedData['username'] != $account->username) { if((Account::where('domain',$domain)->where('username',$validatedData['username'])->count()) == 0) { $account->username = $validatedData['username']; } } $account->enabled = $validatedData['enabled']; $account->sendonly = $validatedData['sendonly']; $account->quota = $validatedData['quota']; if(!empty($validatedData['password'])) { if(Entropy::Calculate($validatedData['password']) < static::MinimumEntropy) { throw new ValidationException('Password is not complex enough'); } // encode password $hash = DovecotPw::Encrypt($validatedData['password']); $account->password = $hash; } $account->save(); return [$account,$hash]; } elseif($action == 'delete') { $validatedData = $request->validate([ 'id' => ['required','integer'], ]); $account = Account::Find($validatedData['id']); $account->delete(); return []; } else { return response(['fail' => 'action', 'errors' => ['Action unknown: '. $action]],400); } } catch(ValidationException $v) { return response(['fail' => 'validation', 'errors' => $v->errors()],400); } catch(PermissionException $x) { return response(['fail' => 'role', 'errors' => ['Action requires role '. $x->role()]],403); } catch(ErrorException $v) { return response(['fail' => 'errors', 'errors' => $v->errors()],400); } } public function siteAccountsAjax(Request $request) { $action = $request->input('action'); try { // Allow only admin if(!(Auth::user()->hasRole('admin'))) { throw PermissionException('Not Authorized');} if($action == 'create') { $validatedData = $request->validate([ 'username' => ['required', 'string',], 'enabled' => ['required', 'boolean',], 'sendonly' => ['required', 'boolean',], 'quota' => ['required', 'integer',], 'password' => ['required', 'string',], ]); if(Account::where('username',$validatedData['username'])->where('domain', '')->count() > 0){ throw new ValidationException('Account already exists'); } // encode password if(Entropy::Calculate($validatedData['password']) < static::MinimumEntropy) { throw new ValidationException('Password is not complex enough'); } $hash = DovecotPw::Encrypt($validatedData['password']); $account = Account::Create([ 'username' => $validatedData['username'], 'domain' => "localhost", 'enabled' => $validatedData['enabled'], 'sendonly' => $validatedData['sendonly'], 'quota' => $validatedData['quota'], 'password' => $hash, ]); $account->save(); return [$account]; } elseif($action == 'checkusername') { $validatedData = $request->validate([ 'username' => ['required', 'string', ''], 'id' => ['nullable','integer'], ]); if(!empty($validatedData['id'])) { $a = Account::Find($validatedData['id']); if($validatedData['username'] == $a->username) { return true; } } return (Account::where('domain',"localhost")->where('username',$validatedData['username'])->count() == 0); } elseif($action == 'list') { $accounts = Account::where('domain',"localhost")->get(); return $accounts; } elseif($action == 'update') { $validatedData = $request->validate([ 'id' => ['required','integer'], 'username' => ['nullable', 'string',], 'enabled' => ['required', 'boolean',], 'sendonly' => ['required', 'boolean',], 'quota' => ['required', 'integer',], 'password' => ['nullable', 'string',], ]); $account = Account::Find($validatedData['id']); if(!empty($validatedData['username']) && $validatedData['username'] != $account->username) { if((Account::where('domain',"localhost")->where('username',$validatedData['username'])->count()) == 0) { $account->username = $validatedData['username']; } } $account->enabled = $validatedData['enabled']; $account->sendonly = $validatedData['sendonly']; $account->quota = $validatedData['quota']; if(!empty($validatedData['password'])) { if(Entropy::Calculate($validatedData['password']) < static::MinimumEntropy) { throw new ValidationException('Password is not complex enough'); } // encode password $hash = DovecotPw::Encrypt($validatedData['password']); $account->password = $hash; } $account->save(); return [$account,$hash]; } elseif($action == 'delete') { $validatedData = $request->validate([ 'id' => ['required','integer'], ]); $account = Account::Find($validatedData['id']); $account->delete(); return []; } else { return response(['fail' => 'action', 'errors' => ['Action unknown: '. $action]],400); } } catch(ValidationException $v) { return response(['fail' => 'validation', 'errors' => $v->errors()],400); } catch(PermissionException $x) { return response(['fail' => 'role', 'errors' => ['Action requires role '. $x->role()]],403); } catch(ErrorException $v) { return response(['fail' => 'errors', 'errors' => $v->errors()],400); } } private function tidyAliases($aliases) { // figure out how to merge $a_list = []; foreach($aliases as $a) { if(empty($a_list[$a->source_username])) { $a_list[$a->source_username] = new \stdClass; $a_list[$a->source_username]->source = $a->source_username; $a_list[$a->source_username]->dest = []; } $a->destination = empty($a->destination_domain)?($a->destination_username):($a->destination_username.'@'.$a->destination_domain); $a_list[$a->source_username]->dest[] = $a; } return array_values($a_list); } public function aliasesAjax(Request $request) { $action = $request->input('action'); try { // block unverified users if(!(Auth::user()->hasRole('admin') || Auth::user()->hasRole('user'))){ throw PermissionException('Not Authorized'); } $firstValidatedData = $request->validate([ 'domain' => ['required', 'string', 'exists:App\Domain,domain'], ]); $domain = Domain::where('domain',$firstValidatedData['domain'])->first(); if(!Auth::user()->hasRole('admin')){ // if not an admin, check if edit roles are enabled for this domain $domain->needRole('edit'); // throws exception if curren user does not have this role } if($action == 'checksource') { $validatedData = $request->validate([ 'source' => ['required', 'string', ''], 'id' => ['nullable','integer'], ]); if(!empty($validatedData['id'])) { $a = Alias::Find($validatedData['id']); if($validatedData['source_username'] == $a->source) { return true; } } return (Alias::where('source_domain',$domain->domain)->where('source_username',$validatedData['source'])->count() == 0); } elseif($action == 'list') { return $this->tidyAliases($domain->aliases); } elseif($action == 'add_dest') { $validatedData = $request->validate([ 'source' => ['required','string'], 'destination' => ['nullable', 'string',], 'enabled' => ['required', 'boolean',], 'comment' => ['nullable', 'string',], ]); $dparts = explode('@',$validatedData['destination'],2); $a = Alias::Create([ 'source_username' => $validatedData['source'], 'source_domain' => $domain->domain, 'destination_username' => $dparts[0], 'destination_domain' => isset($dparts[1])?$dparts[1]:null, 'enabled' => $validatedData['enabled'], 'comment' => $validatedData['comment'], ]); $a->save(); return $this->tidyAliases([$a]); } elseif($action == 'update_dest') { $validatedData = $request->validate([ 'id' => ['required','integer'], 'destination' => ['nullable', 'string',], 'enabled' => ['required', 'boolean',], 'comment' => ['nullable', 'string',], ]); $a = Alias::Find($validatedData['id']); $dparts = explode('@',$validatedData['destination'],2); $a->destination_username = $dparts[0]; $a->destination_domain = isset($dparts[1])?$dparts[1]:null; $a->enabled = $validatedData['enabled']; $a->comment = $validatedData['comment']; $a->save(); return $this->tidyAliases([$a]); } elseif($action == 'delete_dest') { $validatedData = $request->validate([ 'id' => ['required','integer'], ]); $a = Alias::Find($validatedData['id']); $a->delete(); return []; } elseif($action == 'delete_src') { $validatedData = $request->validate([ 'source' => ['required','string'], ]); $aliases = Alias::where('source_domain',$domain->domain)->where('source_username',$validatedData['source']); foreach($aliases as $a) { $a->delete(); } return []; } else { return response(['fail' => 'action', 'errors' => ['Action unknown: '. $action]],400); } } catch(ValidationException $v) { return response(['fail' => 'validation', 'errors' => $v->errors()],400); } catch(PermissionException $x) { return response(['fail' => 'role', 'errors' => ['Action requires role '. $x->role()]],403); } catch(ErrorException $v) { return response(['fail' => 'errors', 'errors' => $v->errors()],400); } } }