Skip to content

Commit be8f5c7

Browse files
authored
Merge pull request #7756 from TheThingsNetwork/fix/device_count_on_device_creation
Rate limit device creation by application ID instead of device ID
2 parents 3d0d2f1 + 419720f commit be8f5c7

12 files changed

Lines changed: 600 additions & 0 deletions

pkg/ttnpb/application_interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ func (m *CreateApplicationRequest) IDString() string {
7474
return m.GetApplication().IDString()
7575
}
7676

77+
// RateLimitKey is the implementation of the RateLimitKeyer interface.
78+
func (*CreateApplicationRequest) RateLimitKey() string {
79+
return ""
80+
}
81+
7782
func (m *UpdateApplicationRequest) IDString() string {
7883
return m.GetApplication().IDString()
7984
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright © 2025 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ttnpb_test
16+
17+
import (
18+
"testing"
19+
20+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
21+
)
22+
23+
func TestCreateApplicationRequest_RateLimitKey(t *testing.T) {
24+
t.Parallel()
25+
tests := []struct {
26+
name string
27+
req *ttnpb.CreateApplicationRequest
28+
expected string
29+
}{
30+
{
31+
name: "valid request with user collaborator",
32+
req: &ttnpb.CreateApplicationRequest{
33+
Application: &ttnpb.Application{
34+
Ids: &ttnpb.ApplicationIdentifiers{
35+
ApplicationId: "test-app-1",
36+
},
37+
},
38+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
39+
Ids: &ttnpb.OrganizationOrUserIdentifiers_UserIds{
40+
UserIds: &ttnpb.UserIdentifiers{
41+
UserId: "test-user",
42+
},
43+
},
44+
},
45+
},
46+
expected: "",
47+
},
48+
{
49+
name: "different app same user",
50+
req: &ttnpb.CreateApplicationRequest{
51+
Application: &ttnpb.Application{
52+
Ids: &ttnpb.ApplicationIdentifiers{
53+
ApplicationId: "test-app-2",
54+
},
55+
},
56+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
57+
Ids: &ttnpb.OrganizationOrUserIdentifiers_UserIds{
58+
UserIds: &ttnpb.UserIdentifiers{
59+
UserId: "test-user",
60+
},
61+
},
62+
},
63+
},
64+
expected: "",
65+
},
66+
{
67+
name: "valid request with organization collaborator",
68+
req: &ttnpb.CreateApplicationRequest{
69+
Application: &ttnpb.Application{
70+
Ids: &ttnpb.ApplicationIdentifiers{
71+
ApplicationId: "test-app-3",
72+
},
73+
},
74+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
75+
Ids: &ttnpb.OrganizationOrUserIdentifiers_OrganizationIds{
76+
OrganizationIds: &ttnpb.OrganizationIdentifiers{
77+
OrganizationId: "test-org",
78+
},
79+
},
80+
},
81+
},
82+
expected: "",
83+
},
84+
{
85+
name: "nil collaborator",
86+
req: &ttnpb.CreateApplicationRequest{
87+
Application: &ttnpb.Application{
88+
Ids: &ttnpb.ApplicationIdentifiers{
89+
ApplicationId: "test-app",
90+
},
91+
},
92+
},
93+
expected: "",
94+
},
95+
{
96+
name: "nil request",
97+
req: &ttnpb.CreateApplicationRequest{},
98+
expected: "",
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
t.Parallel()
105+
got := tt.req.RateLimitKey()
106+
if got != tt.expected {
107+
t.Errorf("RateLimitKey() = %v, want %v", got, tt.expected)
108+
}
109+
})
110+
}
111+
}

pkg/ttnpb/client_interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func (m *CreateClientRequest) IDString() string {
5858
return m.GetClient().IDString()
5959
}
6060

61+
// RateLimitKey is the implementation of the RateLimitKeyer interface.
62+
func (*CreateClientRequest) RateLimitKey() string {
63+
return ""
64+
}
65+
6166
func (m *UpdateClientRequest) IDString() string {
6267
return m.GetClient().IDString()
6368
}

pkg/ttnpb/client_ratelimit_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright © 2025 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ttnpb_test
16+
17+
import (
18+
"testing"
19+
20+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
21+
)
22+
23+
func TestCreateClientRequest_RateLimitKey(t *testing.T) {
24+
t.Parallel()
25+
tests := []struct {
26+
name string
27+
req *ttnpb.CreateClientRequest
28+
expected string
29+
}{
30+
{
31+
name: "valid request with user collaborator",
32+
req: &ttnpb.CreateClientRequest{
33+
Client: &ttnpb.Client{
34+
Ids: &ttnpb.ClientIdentifiers{
35+
ClientId: "test-client-1",
36+
},
37+
},
38+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
39+
Ids: &ttnpb.OrganizationOrUserIdentifiers_UserIds{
40+
UserIds: &ttnpb.UserIdentifiers{
41+
UserId: "test-user",
42+
},
43+
},
44+
},
45+
},
46+
expected: "",
47+
},
48+
{
49+
name: "different client same user",
50+
req: &ttnpb.CreateClientRequest{
51+
Client: &ttnpb.Client{
52+
Ids: &ttnpb.ClientIdentifiers{
53+
ClientId: "test-client-2",
54+
},
55+
},
56+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
57+
Ids: &ttnpb.OrganizationOrUserIdentifiers_UserIds{
58+
UserIds: &ttnpb.UserIdentifiers{
59+
UserId: "test-user",
60+
},
61+
},
62+
},
63+
},
64+
expected: "",
65+
},
66+
{
67+
name: "valid request with organization collaborator",
68+
req: &ttnpb.CreateClientRequest{
69+
Client: &ttnpb.Client{
70+
Ids: &ttnpb.ClientIdentifiers{
71+
ClientId: "test-client-3",
72+
},
73+
},
74+
Collaborator: &ttnpb.OrganizationOrUserIdentifiers{
75+
Ids: &ttnpb.OrganizationOrUserIdentifiers_OrganizationIds{
76+
OrganizationIds: &ttnpb.OrganizationIdentifiers{
77+
OrganizationId: "test-org",
78+
},
79+
},
80+
},
81+
},
82+
expected: "",
83+
},
84+
{
85+
name: "nil collaborator",
86+
req: &ttnpb.CreateClientRequest{
87+
Client: &ttnpb.Client{
88+
Ids: &ttnpb.ClientIdentifiers{
89+
ClientId: "test-client",
90+
},
91+
},
92+
},
93+
expected: "",
94+
},
95+
{
96+
name: "nil request",
97+
req: &ttnpb.CreateClientRequest{},
98+
expected: "",
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
t.Parallel()
105+
got := tt.req.RateLimitKey()
106+
if got != tt.expected {
107+
t.Errorf("RateLimitKey() = %v, want %v", got, tt.expected)
108+
}
109+
})
110+
}
111+
}

pkg/ttnpb/end_device.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,11 @@ func (m *EndDevice) IDString() string {
29902990
return m.GetIds().IDString()
29912991
}
29922992

2993+
// RateLimitKey is the Implementation of the RateLimitKeyer interface.
2994+
func (*CreateEndDeviceRequest) RateLimitKey() string {
2995+
return ""
2996+
}
2997+
29932998
// All ExtractRequestFields methods are used by github.com/grpc-ecosystem/go-grpc-middleware/tags.
29942999

29953000
func (m *ResetAndGetEndDeviceRequest) ExtractRequestFields(dst map[string]interface{}) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright © 2025 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ttnpb_test
16+
17+
import (
18+
"testing"
19+
20+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
21+
)
22+
23+
func TestCreateEndDeviceRequest_RateLimitKey(t *testing.T) {
24+
t.Parallel()
25+
tests := []struct {
26+
name string
27+
req *ttnpb.CreateEndDeviceRequest
28+
expected string
29+
}{
30+
{
31+
name: "valid request",
32+
req: &ttnpb.CreateEndDeviceRequest{
33+
EndDevice: &ttnpb.EndDevice{
34+
Ids: &ttnpb.EndDeviceIdentifiers{
35+
ApplicationIds: &ttnpb.ApplicationIdentifiers{
36+
ApplicationId: "test-app",
37+
},
38+
DeviceId: "test-device-1",
39+
},
40+
},
41+
},
42+
expected: "",
43+
},
44+
{
45+
name: "different device same app",
46+
req: &ttnpb.CreateEndDeviceRequest{
47+
EndDevice: &ttnpb.EndDevice{
48+
Ids: &ttnpb.EndDeviceIdentifiers{
49+
ApplicationIds: &ttnpb.ApplicationIdentifiers{
50+
ApplicationId: "test-app",
51+
},
52+
DeviceId: "test-device-2",
53+
},
54+
},
55+
},
56+
expected: "",
57+
},
58+
{
59+
name: "nil request",
60+
req: &ttnpb.CreateEndDeviceRequest{},
61+
expected: "",
62+
},
63+
}
64+
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
t.Parallel()
68+
got := tt.req.RateLimitKey()
69+
if got != tt.expected {
70+
t.Errorf("RateLimitKey() = %v, want %v", got, tt.expected)
71+
}
72+
})
73+
}
74+
}

pkg/ttnpb/gateway_interfaces.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ func (m *CreateGatewayRequest) IDString() string {
7474
return m.GetGateway().IDString()
7575
}
7676

77+
// RateLimitKey is the implementation of the RateLimitKeyer interface.
78+
func (*CreateGatewayRequest) RateLimitKey() string {
79+
return ""
80+
}
81+
7782
func (m *UpdateGatewayRequest) IDString() string {
7883
return m.GetGateway().IDString()
7984
}

0 commit comments

Comments
 (0)