Mi abuela es una señora muy adelantada a su tiempo y con muchas inquietudes sobre las nuevas tecnologías. No hace mucho le dio por empezar a programar en C# y a veces las dudillas que le surgen me las pregunta.
El otro día tuvimos una interesante conversación sobre el licenciamiento y me he decidido a intentar transcribir nuestra conversación en una entrada. Puede ser un poco larga pero de seguro que merece la pena.
- Pues mira nieto, el otro día hice una aplicación para gestionar residencias de ancianos. Parece que tiene buena acogida y que gusta, pero con el esfuerzo que me ha supuesto, no quisiera que se pusieran a copiársela entre las residencias. Que esos son muy listos para aprovecharse de los pobres ancianos. ¿Cómo puedo hacer para evitar que se lo copien mi aplicacioncita de unos a otros?
- Claro que si abuela. Me imagino lo mal que se tiene que sentir uno al encontrarse sus cosas en el emule, torrents o RapidShares de turno…
- Niño, ¿de que me hablas? Yo me refería que se lo mandan por mail o los copian en CDs…
- Claro que si abuela. De hecho .NET tiene modelos que te permiten proteger, o mejor dicho, licenciar tus aplicaciones de manera muy sencilla y flexible. Si quieres te explico cómo se hace.
- Claro que si nieto. Pero explícamelo sencillito que tu ya sabes que a mi me cuesta un poco con estas edades…
- Bueno, bueno. A ver abuela. ¿Sabes de lo que hablo si digo atributos de clase?
- Pues no estoy del todo segura. Anda, recuérdamelo. Y mejor si es con un ejemplillo.
- Bueno abuela. Los atributos de clase son unos modificadores que se colocan justo antes de definir las clases. Y se colocan entre corchetes. Hay muchos atributos de clase, de hecho, puedes usar varios para una misma clase. Por ejemplo, este atributo sirve para indicar que una clase es serializable.
1: [Serializable]
2: class Foo
3: {
4: //...
5: }
Como ya te he comentado, abuela, hay muchos más. Pero el que a nosotros nos interesa para el licenciamiento se llama System.ComponentModel.LicenseProvider.
- O sea, que las protecciones se hacen usando ese atributo de clase, ¿no?
- Exactamente abuela. Cada vez que usemos este atributo en una clase, significará que esta estará protegida por una licencia.
- Vale. Entonces la clase que yo quiera proteger debe quedar más o menos así, ¿no?
1: [System.ComponentModel.LicenseProvider]
2: public class Foo
3: {
4: // ...
5: }
Bien, nietecito. Hasta aquí lo entiendo. Aunque me temo que lo que sigue a partir de ahora será más complicado, ¿verdad?
- Que va abuela. Mira. Verás que sencillo. Cuando una clase viene con ese atributo, significa que va a recibir un objeto licencia. Esta licencia es una instancia de la clase System.ComponentModel.License y es muy sencillita:
1: namespace System.ComponentModel
2: {
3: public abstract class License : IDisposable
4: {
5: protected License();
6: public abstract string LicenseKey { get; }
7: public abstract void Dispose();
8: }
9: }
Lo interesante de esta clase es que es abstracta, que implementa la interfaz IDisposable y su propiedad LicenseKey es de sólo lectura.
- Ya veo. Entonces voy a recibir una instancia de "algo" que hereda de System.ComponentModel.License. Porque como dices, es una clase abstracta. Lo que no me queda claro es quién crea esa instancia ni cómo lo hace.
- Ahora vamos a eso, abuela. El sistema de licenciamiento que tiene .NET funciona de la siguiente manera: La clase que quieres proteger va a recibir un objeto que hereda de License. Este objeto License es generado por una entidad proveedora de licencias a través del gestor de licencias que tiene el framework de .NET.
Explicado de otra manera. Cuando la clase que quieres licenciar se esta creando, le pide al gestor de licencias (System.ComponentModel.LicenseManager) una licencia. Este entregará dicha licencia haciendo uso del generador o proveedor de licencias (System.ComponentModel.LicenseProvider)
- O sea,que LicenseManager va a retornar la licencia que a su vez retorne LicenseProvider a la clase a proteger. Aquí hay gato encerrado o hay algo que todavía no me has contado. La clase License es abstracta, asi que me falta una clase License que en cualquier caso, parece que siempre la devuelve LicenseProvider. Con lo cual, no veo que hay de bueno en todo esto.
- Claro abuela, te falta añadirle toda la lógica del sistema de licenciamiento. El framework de .NET te da la base y tu la completas.
- Ay madre, que ya me asustas. ¿Cómo es eso de que yo tengo que poner la lógica a todo esto?
- Que no es para tanto, abuela. Lo que hay que hacer es crearse una clase que permita devolver las licencias que queramos usando la lógica que necesitemos. Y por supuesto, la licencia que esta devuelva podrá ser como queramos siempre y cuando herede de System.ComponentModel.License.
La clase generadora de licencias se le pasará como argumento al atributo de clase System.ComponentModel.LicenseProvider. De manera que el proveedor pueda invocar a nuestra clase generadora para obtener la licencia correspondiente.
- Oye. ¿Y es muy difícil construir una clase generadora de licencias de estas? Me vas a tener que enseñar un ejemplo de esto que me estás contando, ¿eh?
- Bueno, tan difícil como queramos hacerlo nosotros. Microsoft de hecho nos dejó una creada muy sencillita. De hecho es tan sencillita que se aconseja no usarla para nuestros desarrollos. Esta clase es System.LicenseProvider.LicFileLicenseProvider.
Te enseño un ejemplo de cómo se usaría este generador:
1: [System.ComponentModel.LicenseProvider(typeof(System.ComponentModel.LicFileLicenseProvider))]
2: public class Foo
3: {
4: private System.ComponentModel.License _license = null;
5:
6: public Foo()
7: {
8: _license = System.ComponentModel.LicenseManager.Validate(this.GetType(), this);
9: }
10: }
Concretamente LicFileLicenseProvider funciona de la siguiente manera:
1. En la carpeta donde se encuentra el ensamblado con la clase a proteger, busca un fichero con extensión .lic y nombre igual que el nombre del namespace con la clase a proteger, un punto y el nombre de la clase a proteger.
2. El fichero .lic debe tener el siguiente contenido de texto: "Namespace+clase is a licensed component.", donde Namespace + clase es el nombre del namespace de la clase que queremos proteger.
- Entiendo, entiendo. Pero no veo por qué no quieres que se use ese tipo de proveedor de licencias.
- ¡Abuela! Cualquiera que supiese que estas usando ese licenciamiento puede crearse manualmente el fichero .lic y… adiós protección.
- Bueno, bueno. Por cierto, ¿qué pasa si el torpe de turno va y borra o modifica el contenido de ese fichero?
- En ese caso, la llamada en el constructor de tu clase a Validate() fallará y lanzará una excepción de tipo System.ComponentModel.LicenseException. Si no la capturas, el licenciamiento habrá funcionado, puesto que no se ha creado ninguna instancia de la clase protegida, pero posiblemente el mensaje de error que lance la aplicación no será muy descriptivo para el usuario.
Las excepciones, cuando se producen, suelen ser bastante lentas, así que si únicamente quieres saber si existe licencia, puedes usar el método IsValid, en lugar de Validate. Este te retornará un booleano indicando si la licencia es valida o no. Pero no te devolverá ninguna licencia.
- Entonces, realmente no necesito ninguna clase License. Me podría bastar con llamar a IsVaild y así saber si se puede ejecutar o no la aplicación, ¿no?
- Bueno, abuela, de eso se trata. El modelo es así de flexible. Si no lo necesitas, no lo uses. Pero fíjate lo que se puede llegar a montar. Imagínate que a pesar de no tener una licencia buena, quisieras permitir que la aplicación se ejecutara. Por ejemplo, como una versión de prueba. O que dependiendo de la licencia, se pudieran ejecutar unas u otras opciones de la aplicación.
- ¡Pues claro! Así si que sería fácil, por ejemplo, vender por separado el modulo de gestión de enfermeros y por otro lado la gestión de las medicinas… ¡Realmente interesante!
- Exacto abuela. Y en la propiedad License.LicenseKey podrías poner el identificador del cliente.
Resumiendo. Tenemos 4 actores en esto de la gestión de licencias: La clase que queremos licenciar, el tipo de licencia, el proveedor de licencias y el gestor de licencias.
- Si, lo voy entendiendo. Pero, ¿cómo podría crearme mi propio proveedor de licencias?
- No es difícil, abuela. Basta con crearse una clase que herede de LicenseProvider y otra que herede de License:
1: using System.ComponentModel;
2:
3: /// My License Provider class
4: class MyLicenseProvider : LicenseProvider
5: {
6: public override License GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
7: {
8: return new MyLicense();
9: }
10: }
11:
12: // My License class
13: class MyLicense : License
14: {
15: public override string LicenseKey
16: {
17: get { return "Some license key"; }
18: }
19: }
Este es un ejemplo muy sencillo. Realmente no estamos haciendo ninguna comprobación de nada. Siempre devuelve una licencia buena.
- Ya creo que me voy enterando. De todas maneras no estoy muy segura de la propiedad LicenseKey de la licencia. Si es de sólo lectura y toda la lógica para la generación de la licencia reside en el proveedor. Entonces le tendré que pasar el valor del LicenseKey a través del constructor, ¿no? Sino, no lo veo yo. Luego dependiendo de ese LicenseKey la aplicación permitiría o no hacer determinadas acciones. ¿Cierto?
Más o menos esta sería mi clase Licencia, que recibiría el License Key a través del constructor:
1: class MyLicense : License
2: {
3: private string _licenseKey = null;
4:
5: public MyLicense(string licenseKey)
6: {
7: _licenseKey = licenseKey;
8: }
9:
10: public override string LicenseKey
11: {
12: get { return _licenseKey; }
13: }
14:
15: public override void Dispose()
16: {
17: }
18: }
Mi clase Proveedora de licencias que, a modo de ejemplo, devuelve una licencia siempre con la misma clave:
1: using System.ComponentModel;
2:
3: class MyLicenseProvider : LicenseProvider
4: {
5: public override License GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
6: {
7: return new MyLicense("Full license");
8: }
9: }
Y por último mi clase protegida. En este caso quiero proteger la llamada a DoSomething chequeando el valor de la clave de licencia:
1: using System.ComponentModel;
2:
3: [LicenseProvider(typeof(MyLicenseProvider))]
4: public class Foo
5: {
6: private License _license = null;
7:
8: public Foo()
9: {
10: _license = LicenseManager.Validate(this.GetType(), this);
11: }
12:
13: public void DoSomething()
14: {
15: if (_license.LicenseKey != "Full license")
16: {
17: throw new LicenseException(this.GetType(), this,
18: "You don´t have a valid license to invoke this method.");
19: }
20:
21: // Do something here ...
22: }
23: }
- Bueno abuela, claro que lo podrías hacer así. Pero te estarías cargando toda la filosofía del modelo de licencias. Si en cualquier momento quisieras cambiar la forma de proteger tu aplicación, tendrías que modificar en todos los sitios de la clase protegida donde se comprueba el valor de la clave de licencia.
Realmente a la aplicación, o en tu ejemplo, a la clase protegida, le da absolutamente igual el valor de LicenseKey. El hecho de que se haya podido llamar a la función ya significa que hay una licencia valida pues el constructor ya la ha validado.
- Ah! Entonces también la lógica de saber si las claves de licencia son buenas o no, de si se puede ejecutar cierta función y demás, se hace también en el proveedor. ¿Cierto?
- Exactamente. Piensa en tu licencia como un contrato o un permiso de utilización de la clase protegida. Si tiene licencia, funciona, sino, ni siquiera puede existir. Además, si fuera necesario, podría indicar determinadas limitaciones.
- Comprendo, comprendo. Es como tu carnet de conducir, ¿no? Si tienes licencia, puedes llevar el coche, y si no, no. Pero además, tu carnet también te limita los vehículos que puedes conducir, ¿verdad?
- Eso es abuela, muy buen ejemplo. Me lo tengo que apuntar para cuando se lo tenga que explicar a otras personas. Jejeje.
- Entonces el ejemplo que he dicho antes no sirve. Mejor sería poner algo así:
Mi licencia es capaz de indicar si se está en modo demostración. En este modo habrá operaciones que no se pueden ejecutar:
1: class MyLicense : License
2: {
3: private string _licenseKey = null;
4:
5: public MyLicense(string licenseKey, bool isDemoVersion)
6: {
7: _licenseKey = licenseKey;
8: this.IsDemoVersion = isDemoVersion;
9: }
10:
11: public override string LicenseKey
12: {
13: get { return _licenseKey; }
14: }
15:
16: public bool IsDemoVersion { get; private set; }
17:
18: public override void Dispose()
19: {
20: }
21: }
Mi clase proveedora de licencias determina si la licencia a generar es de tipo demostración o no. En mi ejemplo, simplemente es de demostración a partir de 2013:
1: class MyLicenseProvider : LicenseProvider
2: {
3: public override MyLicense GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
4: {
5: // Demo version since 2013
6: if (DateTime.Now.CompareTo(new DateTime(2013, 1, 1)) > 0)
7: {
8: return new MyLicense("Full license", true);
9: }
10: else
11: {
12: return new MyLicense("Full license", false);
13: }
14: }
15: }
Por último, la clase que estoy protegiendo ya no tiene que tener ninguna lógica respecto a la licencia. Sólo chequear que no está en modo demostración:
1: [LicenseProvider(typeof(MyLicenseProvider))]
2: public class Foo
3: {
4: private MyLicense _license = null;
5:
6: public Foo()
7: {
8: _license = LicenseManager.Validate(this.GetType(), this) as MyLicense;
9: }
10:
11: public void DoSomething()
12: {
13: // Demo version cannot execute this code!!!
14: if (_license.IsDemoVersion)
15: {
16: throw new LicenseException(this.GetType(), this,
17: "You don´t have a valid license to invoke this method.");
18: }
19:
20: // Do something here ...
21: }
22: }
- Mucha mejor pinta, abuela. Tampoco podemos olvidar que la llamada a Validate nos ofrece otros argumentos la mar de interesantes. Vamos a verlos.
- LicenseContext context. Este objeto nos da el contexto en el cual se va a usar la clase a proteger. Quizás su propiedad más importante sea UsageMode, el cual nos va a permitir saber si estamos ejecutando una aplicación normal de escritorio o bien si estamos usando la clase en modo diseño dentro de Visual Studio. Por ejemplo, para licenciar componentes o controles gráficos para otros desarrolladores.
- Type type. Nos da el tipo de la clase a licenciar. Así podemos usar el mismo proveedor de licencias para proteger varias clases.
- object instance. Si no fuese sufieciente saber el tipo, tambien tenemos a nuestra disposición la instancia de la clase a proteger.
- bool allowExceptions. Nos indica si debemos lanzar una excepción LicenseException en caso de no cumplir con la licencia. Y esto tiene sentido pues acuerdate de la llamada IsValid que no lanza excepciones. Básicamente Validate e IsValid usan la llamada a esta función. Una permite excepciones y la otra no.
Sin meternos en historias de dar soporte a distintos tipos de clases a proteger ni nada de eso, tu clase proveedora de licencias quedaría mejor así:
1: class MyLicenseProvider : LicenseProvider
2: {
3: public override MyLicense GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
4: {
5: // Full license until 2013
6: if (DateTime.Now.CompareTo(new DateTime(2013, 1, 1)) < 0)
7: {
8: return new MyLicense("Full license", false);
9: }
10: // Demo version between 2013 - 2014
11: else if (DateTime.Now.CompareTo(new DateTime(2014, 1, 1)) < 0)
12: {
13: return new MyLicense("Demo version", true);
14: }
15: // No license from 2014
16: else
17: {
18: if (allowExceptions)
19: {
20: throw new LicenseException(type, instance, "Invalid license.");
21: }
22: else
23: {
24: return null;
25: }
26: }
27: }
Y así nuestra clase protegida puede usar IsValid en lugar de Validate sin preocuparse de si se van a producir excepciones.
Es muy importante que te quede claro que toda la lógica de la licencia debe quedar dentro de la clase de generadora de licencias. Esta puede hacer todo lo que se te ocurra. Desde leer un fichero de texto como hace LicFileLicenseProvider, hasta conectarse a un servidor remoto en otro continente para obtener la licencia.
- Pues si nietecito, ya tengo más o menos claro todo esto de las licencias. Ya puedo estar segura de que nadie se va a copiar mi programa de residencias de ancianos.
- Me alegro de haberte ayudado abuela. De todas maneras, siempre habrá alguien que encuentre la manera de saltarse la licencia. Pero todo dependerá de lo importante que sea lo que tengamos que proteger y el tiempo que le queramos dedicar poniendo trampas y demás dificultades a estos individuos. Recuerda, no hace falta crear la super licencia que protege los sistemas del Pentágono. La mayoría de las personas lo dará por imposible sólo al ver que ya tiene licencia. Y eso es lo importante.