@@ -164,14 +164,54 @@ def setQuota(self, key, value):
164164class SmppsCredential (CredentialGeneric ):
165165 """Credential set for SMPP Server connection"""
166166
167+ # Default IP whitelist matches everything (current behaviour — no regression
168+ # when the attribute is absent on pickled users from older versions).
169+ DEFAULT_IP_WHITELIST = '0.0.0.0/0'
170+
167171 def __init__ (self , default_authorizations = True ):
168172 if not isinstance (default_authorizations , bool ):
169173 default_authorizations = False
170174
171- self .authorizations = {'bind' : default_authorizations , }
175+ # `bind`: boolean, same as before.
176+ # `ip` : comma-separated list of IPv4/IPv6 addresses or CIDRs. The
177+ # remote peer IP of each bind request must fall inside at
178+ # least one of these networks, or the bind is rejected.
179+ # Default is "any IPv4" so upgrading doesn't lock users out.
180+ self .authorizations = {
181+ 'bind' : default_authorizations ,
182+ 'ip' : self .DEFAULT_IP_WHITELIST ,
183+ }
172184
173185 self .quotas = {'max_bindings' : None }
174186
187+ def setAuthorization (self , key , value ):
188+ """Per-key validation. `bind` is a bool; `ip` is a CIDR/IP whitelist."""
189+ if key == 'ip' :
190+ # Back-compat: SmppsCredential instances pickled before the `ip`
191+ # authorization existed won't have it in their `authorizations`
192+ # dict. Accept it here regardless so `user -u ... ip <cidr>` works
193+ # on legacy users (the getAuthorization fallback mirrors this).
194+ from jasmin .tools .ipmatch import validate_whitelist
195+ ok , err = validate_whitelist (value )
196+ if not ok :
197+ raise jasminApiCredentialError (
198+ 'Authorization ip is not a valid value (%r): %s' % (value , err ))
199+ # Bypass the base class because its validator insists on bool.
200+ self .authorizations [key ] = value
201+ return
202+
203+ if key not in self .authorizations :
204+ raise jasminApiCredentialError ('%s is not a valid Authorization' % key )
205+
206+ # Anything else (e.g. 'bind') must be a bool — delegate to base.
207+ CredentialGeneric .setAuthorization (self , key , value )
208+
209+ def getAuthorization (self , key ):
210+ """Back-compat: older pickled users may lack the `ip` key."""
211+ if key == 'ip' and 'ip' not in self .authorizations :
212+ return self .DEFAULT_IP_WHITELIST
213+ return CredentialGeneric .getAuthorization (self , key )
214+
175215 def setQuota (self , key , value ):
176216 """Additional validation steps"""
177217 if key == 'max_bindings' and value is not None and (value < 0 or not isinstance (value , int )):
@@ -217,6 +257,12 @@ def __init__(self):
217257 'bind_transceiver' : 0 ,
218258 'bind_transmitter' : 0 ,
219259 },
260+ # Live roster of bound peers. Each entry is a dict:
261+ # {'session_id': str, 'peer': '1.2.3.4', 'bind_type': 'bind_transceiver',
262+ # 'bound_at': datetime}
263+ # Populated by SMPPBindManager.addBinding and pruned by removeBinding.
264+ # In-memory only (CnxStatus is not persisted).
265+ 'bound_peer_ips' : [],
220266 'submit_sm_request_count' : 0 ,
221267 'last_activity_at' : 0 ,
222268 'qos_last_submit_sm_at' : 0 ,
0 commit comments