...
- The interface changes resulting from security fixes can severely impair the contracts of the implementing classes. It is even possible that a security fix introduced in a later version is accompanied by modifications to an unrelated interface that must now be implemented by the client. This can prevent the client from implementing the security fix because the new interface may impose additional implementation burden on it.
- If an insider crafts changes to an interface or someone accidentally makes modifications, most of the client code that implements the interface will break resulting in denial of service. This is particularly pernicious in distributed Java based applications.
- Implementers can provide skeletal implementations that the clients can directly extend, however, such code can adversely affect the behavior of the subclasses. When these such default implementations are not provided, the subclasses are forced to provide dummy implementations. This fosters an environment where comments such as 'ignore this code, does nothing', occur incessantly. Such code may never even get tested.
- If there is a security flaw in a public API (consider ThreadGroups, CON01-J. Avoid using ThreadGroup APIs) it will persist throughout the lifetime of the application.
Noncompliant Code Example
In this noncompliant code example, an interface User is frozen with two methods authenticate() and subscribe(). Sometime later, the providers release a free service that does not rely on authentication. The addition of the freeService() method, unfortunately, breaks all the client code that implements the interface. Moreover, the implementers who wish to use only freeService have to face the onus of also providing the other two methods which pollute the API, for reasons discussed earlier.
...
An alternative idea is to prefer abstract classes for dealing with constant evolution, but this comes at the cost of the loss of flexibility that interfaces offer (a class may implement multiple interfaces but extend only one class). One notable pattern is for the provider to distribute an abstract skeletal class that implements the evolving interface. The skeletal class can selectively implement a few methods and force the extending classes to provide concrete implementations of the others. If a new method is added to the interface, the skeletal class can provide a non-abstract default implementation that the extending class can optionally override. This pattern is dangerous because a provider is unaware of the extending class's code and may choose an implementation that introduces security weaknesses in the client API.
| Code Block | ||
|---|---|---|
| ||
public interface User {
boolean authenticate(String username, char[] password);
void subscribe(int noOfDays);
void freeService(); // introducedIntroduced after the classAPI is publicly released
}
abstract class SkeletalUser implements User {
public abstract boolean authenticate(String username, char[] password);
public abstract void subscribe(int noOfDays);
public void freeService() {
/*/ addedAdded later, provide implementation and re-release class*/
}
}
class Client extends SkeletalUser {
// implementsImplements authenticate() and subscribe(), not freeService()
}
|
...
Another compliant solution is to throw an exception from within the new, freeService() method defined in the implementing subclass.
...
Although allowable, a less flexible method compliant solution is to delegate the implementation of the method to subclasses of the client's core interface implementing class.
| Code Block | ||
|---|---|---|
| ||
abstract class Client implements User {
public abstract void freeService(); // delegateDelegate implementation of new method to subclasses
// otherOther concrete implementations
}
|
A variant of compliant solution (2) can also be applied here by throwing an exception from the freeService() method after making the class (and method) non-abstract.
...