Skip to content

Commit

Permalink
Store package-created audit log records + apply rate limit. (dart-lan…
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos authored Jan 18, 2024
1 parent 9995601 commit 52ad4f7
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 34 deletions.
4 changes: 4 additions & 0 deletions app/config/dartlang-pub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ tools:
previewDartSdkPath: '/tool/preview/dart-sdk'
previewFlutterSdkPath: '/tool/preview/flutter'
rateLimits:
- operation: package-created
scope: user
burst: 4
daily: 12
- operation: package-published
scope: package
burst: 3
Expand Down
95 changes: 75 additions & 20 deletions app/lib/audit/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,30 +218,39 @@ class AuditLogRecord extends db.ExpandoModel<String> {
..publishers = [if (publisherId != null) publisherId];
}

factory AuditLogRecord.packagePublished({
static Map<String, dynamic> _dataForPublishing({
required AuthenticatedAgent uploader,
}) {
if (uploader is AuthenticatedGithubAction) {
final runId = uploader.payload.runId;
final sha = uploader.payload.sha;
return <String, dynamic>{
'repository': uploader.payload.repository,
if (runId != null) 'run_id': runId,
if (sha != null) 'sha': sha,
};
} else {
return const {};
}
}

static String _summaryForPublishing({
required AuthenticatedAgent uploader,
required String package,
required String version,
required DateTime created,
String? version,
String? publisherId,
}) {
final summaryParts = <String>[
'Package `$package` version `$version`',
final prefix = <String>[
'Package `$package`',
if (version != null) ' version `$version`',
if (publisherId != null) ' owned by publisher `$publisherId`',
];
var fields = const <String, dynamic>{};
late String summary;
if (uploader is AuthenticatedGithubAction) {
final repository = uploader.payload.repository;
final runId = uploader.payload.runId;
final sha = uploader.payload.sha;
fields = <String, dynamic>{
'repository': repository,
if (runId != null) 'run_id': runId,
if (sha != null) 'sha': sha,
};
summary = [
...summaryParts,
return [
...prefix,
' was published from GitHub Actions',
if (runId != null)
' (`run_id`: [`$runId`](https://github.com/$repository/actions/runs/$runId))',
Expand All @@ -250,29 +259,70 @@ class AuditLogRecord extends db.ExpandoModel<String> {
' to the `$repository` repository.',
].join();
} else if (uploader is AuthenticatedGcpServiceAccount) {
summary = [
...summaryParts,
return [
...prefix,
' was published by Google Cloud service account: `${uploader.payload.email}`.'
].join();
} else {
summary = [
...summaryParts,
return [
...prefix,
' was published by `${uploader.displayId}`.',
].join();
}
}

factory AuditLogRecord.packageCreated({
required AuthenticatedAgent uploader,
required String package,
required DateTime created,
String? publisherId,
}) {
return AuditLogRecord._init()
..created = created
..kind = AuditLogRecordKind.packageCreated
..agent = uploader.agentId
..summary = _summaryForPublishing(
uploader: uploader,
package: package,
publisherId: publisherId,
)
..data = {
'package': package,
if (uploader.email != null) 'email': uploader.email,
if (publisherId != null) 'publisherId': publisherId,
..._dataForPublishing(uploader: uploader),
}
..users = [if (uploader is AuthenticatedUser) uploader.user.userId]
..packages = [package]
..packageVersions = []
..publishers = [if (publisherId != null) publisherId];
}

factory AuditLogRecord.packagePublished({
required AuthenticatedAgent uploader,
required String package,
required String version,
required DateTime created,
String? publisherId,
}) {
return AuditLogRecord()
..id = createUuid()
..created = created
..expires = auditLogRecordExpiresInFarFuture
..kind = AuditLogRecordKind.packagePublished
..agent = uploader.agentId
..summary = summary
..summary = _summaryForPublishing(
uploader: uploader,
package: package,
version: version,
publisherId: publisherId,
)
..data = {
'package': package,
'version': version,
if (uploader.email != null) 'email': uploader.email,
if (publisherId != null) 'publisherId': publisherId,
...fields,
..._dataForPublishing(uploader: uploader),
}
..users = [if (uploader is AuthenticatedUser) uploader.user.userId]
..packages = [package]
Expand Down Expand Up @@ -730,6 +780,11 @@ abstract class AuditLogRecordKind {
/// Event that a package version was updated with new options
static const packageVersionOptionsUpdated = 'package-version-options-updated';

/// Event that a new package was created. This event is created alonside the
/// [packagePublished] event, but is only kept until the default expirty period
/// (to enforce rate limits).
static const packageCreated = 'package-created';

/// Event that a package was published.
///
/// This can be an entirely new package or just a new version to an existing package.
Expand Down
9 changes: 9 additions & 0 deletions app/lib/package/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,7 @@ class PackageBackend {
final newVersion = entities.packageVersion;
final currentDartSdk = await getDartSdkVersion();
final existingPackage = await lookupPackage(newVersion.package);
final isNew = existingPackage == null;

// check authorizations before the transaction
await _requireUploadAuthorization(
Expand Down Expand Up @@ -1036,6 +1037,7 @@ class PackageBackend {
await verifyPackageUploadRateLimit(
agent: agent,
package: newVersion.package,
isNew: isNew,
);

final email = createPackageUploadedEmail(
Expand Down Expand Up @@ -1128,6 +1130,13 @@ class PackageBackend {
...entities.assets,
if (activeConfiguration.isPublishedEmailNotificationEnabled)
outgoingEmail,
if (isNew)
AuditLogRecord.packageCreated(
uploader: agent,
package: newVersion.package,
created: newVersion.created!,
publisherId: package!.publisherId,
),
AuditLogRecord.packagePublished(
uploader: agent,
package: newVersion.package,
Expand Down
19 changes: 15 additions & 4 deletions app/lib/service/rate_limit/rate_limit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,36 @@ import '../../shared/redis_cache.dart';
Future<void> verifyPackageUploadRateLimit({
required AuthenticatedAgent agent,
required String package,
required bool isNew,
}) async {
final operation = AuditLogRecordKind.packagePublished;
final packagePublishedOp = AuditLogRecordKind.packagePublished;

if (agent is AuthenticatedUser) {
await _verifyRateLimit(
rateLimit: _getRateLimit(operation, RateLimitScope.user),
rateLimit: _getRateLimit(packagePublishedOp, RateLimitScope.user),
userId: agent.userId,
);

if (isNew) {
await _verifyRateLimit(
rateLimit: _getRateLimit(
AuditLogRecordKind.packageCreated,
RateLimitScope.user,
),
userId: agent.userId,
);
}
} else {
// apply per-user rate limit on non-user agents as they were package-specific limits
await _verifyRateLimit(
rateLimit: _getRateLimit(operation, RateLimitScope.user),
rateLimit: _getRateLimit(packagePublishedOp, RateLimitScope.user),
package: package,
);
}

// regular package-specific limits
await _verifyRateLimit(
rateLimit: _getRateLimit(operation, RateLimitScope.package),
rateLimit: _getRateLimit(packagePublishedOp, RateLimitScope.package),
package: package,
);
}
Expand Down
48 changes: 48 additions & 0 deletions app/test/frontend/golden/my_activity_log_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,22 @@ <h1 class="title">admin</h1>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
</td>
<td class="summary">
<div class="markdown-body">
<p>
Package
<code>oxygen</code>
was published by
<code>[email protected]</code>
.
</p>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
Expand Down Expand Up @@ -272,6 +288,22 @@ <h1 class="title">admin</h1>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
</td>
<td class="summary">
<div class="markdown-body">
<p>
Package
<code>neon</code>
was published by
<code>[email protected]</code>
.
</p>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
Expand All @@ -290,6 +322,22 @@ <h1 class="title">admin</h1>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
</td>
<td class="summary">
<div class="markdown-body">
<p>
Package
<code>flutter_titanium</code>
was published by
<code>[email protected]</code>
.
</p>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%user-created-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
Expand Down
26 changes: 21 additions & 5 deletions app/test/frontend/golden/pkg_activity_log_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,6 @@ <h3 class="detail-lead-title">Metadata</h3>
</div>
</td>
</tr>
<tr>
<td class="date"></td>
<td class="summary">Only package publication events are retained past 2 months.</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%published-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
Expand Down Expand Up @@ -303,6 +299,26 @@ <h3 class="detail-lead-title">Metadata</h3>
</div>
</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%published-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
</td>
<td class="summary">
<div class="markdown-body">
<p>
Package
<code>oxygen</code>
was published by
<code>[email protected]</code>
.
</p>
</div>
</td>
</tr>
<tr>
<td class="date"></td>
<td class="summary">Only package publication events are retained past 2 months.</td>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%published-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
Expand All @@ -323,7 +339,7 @@ <h3 class="detail-lead-title">Metadata</h3>
</tr>
<tr>
<td class="date">
<a class="-x-ago" href="" title="%%activity-4-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
<a class="-x-ago" href="" title="%%activity-5-date%%" aria-label="%%x-ago%%" aria-role="button" role="button" data-timestamp="%%millis%%">%%x-ago%%</a>
</td>
<td class="summary">
<div class="markdown-body">
Expand Down
Loading

0 comments on commit 52ad4f7

Please sign in to comment.