Skip to content

Commit f1b1de4

Browse files
committed
Improve profile update asset validation
1 parent 08bbe52 commit f1b1de4

1 file changed

Lines changed: 82 additions & 33 deletions

File tree

Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
using Bunkum.Core;
33
using Bunkum.Core.Endpoints;
44
using Bunkum.Core.RateLimit;
5+
using Bunkum.Core.Responses;
56
using Bunkum.Core.Storage;
67
using Bunkum.Listener.Protocol;
78
using Bunkum.Protocols.Http;
89
using Refresh.Common.Constants;
910
using Refresh.Core.Authentication.Permission;
1011
using Refresh.Core.Configuration;
12+
using Refresh.Core.Helpers;
13+
using Refresh.Core.Importing;
1114
using Refresh.Core.RateLimits.Users;
1215
using Refresh.Core.Services;
16+
using Refresh.Core.Types.Assets.Validation;
1317
using Refresh.Core.Types.Data;
1418
using Refresh.Database;
19+
using Refresh.Database.Models.Assets;
1520
using Refresh.Database.Models.Authentication;
1621
using Refresh.Database.Models.Users;
1722
using Refresh.Interfaces.Game.Endpoints.DataTypes.Response;
@@ -73,7 +78,8 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
7378
[NullStatusCode(BadRequest)]
7479
[RateLimitSettings(UserModificationEndpointLimits.TimeoutDuration, UserModificationEndpointLimits.GameRequestAmount,
7580
UserModificationEndpointLimits.BlockDuration, UserModificationEndpointLimits.GameRequestBucket)]
76-
public string? UpdateUser(RequestContext context, DataContext dataContext, GameUser user, string body, GuidCheckerService guidChecker)
81+
public Response UpdateUser(RequestContext context, DataContext dataContext, GameUser user, string body, GuidCheckerService guidChecker,
82+
AssetImporter importer, AipiService aipi)
7783
{
7884
SerializedUpdateData? data = null;
7985

@@ -83,7 +89,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
8389
{
8490
XmlSerializer profileSerializer = new(typeof(SerializedUpdateDataProfile));
8591
if (profileSerializer.Deserialize(new StringReader(body)) is not SerializedUpdateDataProfile profileData)
86-
return null;
92+
return BadRequest;
8793

8894
data ??= profileData;
8995
}
@@ -96,7 +102,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
96102
{
97103
XmlSerializer planetSerializer = new(typeof(SerializedUpdateDataPlanets));
98104
if (planetSerializer.Deserialize(new StringReader(body)) is not SerializedUpdateDataPlanets planetsData)
99-
return null;
105+
return BadRequest;
100106

101107
data ??= planetsData;
102108
}
@@ -108,47 +114,85 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
108114
if (data == null)
109115
{
110116
dataContext.Database.AddErrorNotification("Profile update failed", "Your profile failed to update because the data could not be read.", user);
111-
return null;
117+
return BadRequest;
112118
}
113119

114120
if (data.IconHash != null)
115121
{
116-
//If the icon is a GUID
117-
if (data.IconHash.StartsWith('g'))
118-
{
119-
//Parse out the GUID
120-
long guid = long.Parse(data.IconHash.AsSpan()[1..]);
121-
122-
//If its not a valid GUID, block the request
123-
if (data.IconHash.StartsWith('g') && !guidChecker.IsTextureGuid(dataContext.Game, guid))
124-
{
125-
dataContext.Database.AddErrorNotification("Profile update failed", "Your avatar failed to update because the asset was an invalid GUID.", user);
126-
return null;
127-
}
128-
}
129-
else if (data.IconHash.IsBlankHash())
122+
ValidatedAssetResult iconResult = ResourceValidationHelper.ValidateReference(new(data.IconHash, dataContext, importer, aipi)
130123
{
131-
// Force hash to be a specific value if the icon is supposed to be reset/default to a PSN avatar,
132-
// to not allow uncontrolled values which would still count as blank/empty hash (e.g. unlimited whitespaces)
133-
data.IconHash = "0";
134-
}
135-
else if (!dataContext.DataStore.ExistsInStore(data.IconHash))
124+
MustBeTexture = true,
125+
AssetContextTypeStr = "avatar",
126+
}, context.Logger);
127+
data.IconHash = iconResult.NewAssetRef;
128+
129+
if (iconResult.Status != OK)
136130
{
137-
//If the asset does not exist on the server, block the request
138-
dataContext.Database.AddErrorNotification("Profile update failed", "Your avatar failed to update because the asset was missing on the server.", user);
139-
return null;
131+
if (iconResult.ErrorMessage != null) dataContext.Database.AddErrorNotification("Profile update failed", iconResult.ErrorMessage, user);
132+
return iconResult.Status;
140133
}
141134
}
142135

143-
if (data.LevelLocations != null && data.LevelLocations.Count > 0)
136+
AssetValidationParameters faceParams = new(null!, dataContext, importer, aipi)
144137
{
145-
dataContext.Database.UpdateLevelLocations(data.LevelLocations, user);
138+
MayBeBlank = false,
139+
MayBeGuid = false,
140+
MustBeTexture = true,
141+
AssetContextTypeStr = "image",
142+
};
143+
144+
if (data.YayFaceHash != null)
145+
{
146+
faceParams.AssetRef = data.YayFaceHash;
147+
ValidatedAssetResult yayResult = ResourceValidationHelper.ValidateReference(faceParams, context.Logger);
148+
data.YayFaceHash = yayResult.NewAssetRef;
149+
150+
if (yayResult.Status != OK) return yayResult.Status; // no need to notify, these are always updated in the background
146151
}
147-
148-
if (!string.IsNullOrEmpty(data.PlanetsHash) && data.PlanetsHash != "0" /* Empty planets */ && !dataContext.DataStore.ExistsInStore(data.PlanetsHash))
152+
153+
if (data.MehFaceHash != null)
149154
{
150-
dataContext.Database.AddErrorNotification("Profile update failed", "Your planets failed to update because the asset was missing on the server.", user);
151-
return null;
155+
faceParams.AssetRef = data.MehFaceHash;
156+
ValidatedAssetResult mehResult = ResourceValidationHelper.ValidateReference(faceParams, context.Logger);
157+
data.MehFaceHash = mehResult.NewAssetRef;
158+
159+
if (mehResult.Status != OK) return mehResult.Status;
160+
}
161+
162+
if (data.BooFaceHash != null)
163+
{
164+
faceParams.AssetRef = data.BooFaceHash;
165+
ValidatedAssetResult booResult = ResourceValidationHelper.ValidateReference(faceParams, context.Logger);
166+
data.BooFaceHash = booResult.NewAssetRef;
167+
168+
if (booResult.Status != OK) return booResult.Status;
169+
}
170+
171+
if (data.PlanetsHash != null)
172+
{
173+
// Some LBP2 alpha builds like to insert newlines here
174+
data.PlanetsHash = data.PlanetsHash.Replace("\n", "");
175+
176+
ValidatedAssetResult planetResult = ResourceValidationHelper.ValidateReference(new(data.PlanetsHash, dataContext, importer)
177+
{
178+
// blank = reset planets, but GUIDs should never happen
179+
MayBeGuid = false,
180+
AssetContextTypeStr = "planet asset",
181+
OnNewAssetRefCallback = delegate(string newRef) { data.PlanetsHash = newRef; }
182+
}, context.Logger);
183+
data.PlanetsHash = planetResult.NewAssetRef;
184+
185+
if (planetResult.Status != OK)
186+
{
187+
if (planetResult.ErrorMessage != null) dataContext.Database.AddErrorNotification("Planet update failed", planetResult.ErrorMessage, user);
188+
return planetResult.Status;
189+
}
190+
// TODO also read contents and ensure the asset actually contains an earth and a moon
191+
else if (planetResult.AssetInfo != null && planetResult.AssetInfo.AssetType != GameAssetType.Level)
192+
{
193+
if (planetResult.ErrorMessage != null) dataContext.Database.AddErrorNotification("Planet update failed", "The asset was badly formatted.", user);
194+
return BadRequest;
195+
}
152196
}
153197

154198
// Trim description
@@ -157,8 +201,13 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont
157201
data.Description = data.Description[..UgcLimits.DescriptionLimit];
158202
}
159203

204+
if (data.LevelLocations != null && data.LevelLocations.Count > 0)
205+
{
206+
dataContext.Database.UpdateLevelLocations(data.LevelLocations, user);
207+
}
208+
160209
dataContext.Database.UpdateUserData(user, data, dataContext.Game);
161-
return string.Empty;
210+
return OK;
162211
}
163212

164213
private const int PinTimeoutDuration = 480;

0 commit comments

Comments
 (0)