the browser-facing portion of osu!
at master 2.8 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Models; 7 8use App\Exceptions\InvariantException; 9use Exception; 10 11/** 12 * @property Build $build 13 * @property mixed $disk_md5 14 * @property mixed $mac_md5 15 * @property mixed $osu_md5 16 * @property \Carbon\Carbon $timestamp 17 * @property mixed $unique_md5 18 * @property int $user_id 19 * @property int $verified 20 */ 21class UserClient extends Model 22{ 23 const CREATED_AT = 'timestamp'; 24 25 public $incrementing = false; 26 public $timestamps = false; 27 28 protected $casts = [ 29 'timestamp' => 'datetime', 30 'verified' => 'boolean', 31 ]; 32 protected $primaryKey = ':composite'; 33 protected $primaryKeys = ['user_id', 'osu_md5', 'unique_md5']; 34 protected $table = 'osu_user_security'; 35 36 public static function lookupOrNew($userId, $hash) 37 { 38 $splitHash = static::splitHash($hash); 39 40 return static::firstOrNew([ 41 'user_id' => $userId, 42 'unique_md5' => $splitHash['unique'], 43 'osu_md5' => $splitHash['osu'], 44 ], [ 45 'mac_md5' => $splitHash['mac'], 46 'disk_md5' => $splitHash['disk'], 47 ]); 48 } 49 50 public static function markVerified($userId, $hash) 51 { 52 $client = static::lookupOrNew($userId, $hash); 53 54 try { 55 $client->fill(['verified' => true])->save(); 56 } catch (Exception $e) { 57 if (is_sql_unique_exception($e)) { 58 static::markVerified($userId, $hash); 59 return; 60 } 61 62 throw $e; 63 } 64 } 65 66 public static function splitHash($hash) 67 { 68 $hashes = explode(':', $hash); 69 70 if (count($hashes) < 5) { 71 throw new InvariantException('invalid client hash format'); 72 } 73 74 return array_map(function ($value) { 75 if (!ctype_xdigit($value) || strlen($value) !== 32) { 76 throw new InvariantException('invalid md5 hash inside client hash'); 77 } 78 79 return hex2bin($value); 80 }, [ 81 'osu' => $hashes[0], 82 'mac' => $hashes[2], 83 'unique' => $hashes[3], 84 'disk' => $hashes[4], 85 ]); 86 } 87 88 public function build() 89 { 90 return $this->belongsTo(Build::class, 'osu_md5', 'hash'); 91 } 92 93 public function isLatest() 94 { 95 if ($this->build === null) { 96 return false; 97 } 98 99 $latestBuild = Build::select('build_id') 100 ->where([ 101 'test_build' => false, 102 'stream_id' => $this->build->stream_id, 103 ])->last(); 104 105 return $this->build->getKey() === optional($latestBuild)->getKey(); 106 } 107}