Sesión 3 finalizada

parent b8b96013
Showing with 292 additions and 135 deletions
# Primeros pasos en Solid
Vamos a implementar una aplicación simple en SOLID, en la que vamos a poder autenticarnos y luego ver información de un usuario.
## Requisitos
Todo el software necesario lo tenéis en este fichero, pero para poder ejecutar la aplicación es necesario que esté servida por web. Para ello debemos abrir la aplicación desde un servidor web, que puede ser local, con una instalación propia de Apache o NGINX. Si trabajáis con Linux es tan sencillo como instalar Apache, si estáis en Windows podéis optar por Wampserver o Xamp.
Ubicad estos archivos en el directorio donde se alojarían las páginas web públicas, bajo otro directorio denomiando "solid".
## Crea tu POD
Antes de poder probar la autenticación, es necesario que dispongas de una cuenta registrada en algún servidor que soporte autenticación SOLID. Una opción es https://solid.community. Créate una cuenta ahí y así tendras credenciales y un POD ("vaina" en inglés). Tu pod es el lugar donde mantienes todos tus datos personales, es tu espacio de almacenamiento privado.
## Funcionamiento del código
Al abrir la dirección http://localhost/solid/index.hmtl, tienes la opción de iniciar sesión. Se abrirá otra ventana para autenticarte con alguno de los servicios disponibles. Si te registraste en solid.community, selecciona ese botón y introduce tus credenciales.
La página debería cambiar, mostrando tu WebID y un nuevo botón para cerrar sesión.
¡Ya está! Ya tienes una primera aplicación capaz de autenticar usuarios.
\ No newline at end of file
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Navegador SOLID</title> <title>Navegador SOLID</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="css/main.css">
</head> </head>
<style>
body {
max-width: 800px;
margin: 0 auto;
}
body,
input,
button {
font: 11pt/1.3 "Helvetiva Neue", Helvetica, Arial, sans-serif;
}
button,
label {
font-weight: bold;
}
#profile {
width: 400px;
}
dt {
font-weight: bold;
clear: left;
float: left;
}
dt:after {
content: ': ';
}
a {
color: #7c4dff;
border-bottom: 1px solid;
cursor: pointer;
}
a:hover {
color: #9b79fc;
}
</style>
<body> <body>
<h1>Navegador SOLID</h1>
<div id="login"> <nav class="navbar navbar-light bg-light">
<button>Iniciar sesión</button> <a class="navbar-brand" href="#">
<img src="https://solidproject.org/assets/img/solid-emblem.svg" width="30" height="30" class="d-inline-block align-top" alt="">
Navegador SOLID
</a>
<div class="out-session">
<button id="loginButton" class="btn btn-outline-success my-2 my-sm-0" type="submit">Iniciar sesión</button>
</div> </div>
<div id="logout"> <div class="in-session">
Has iniciado sesión como <strong id="user"></strong>. <strong id="user"></strong>
<button id="logoutButton">Cerrar sesión</button> <button id="logoutButton" class="btn btn-outline-danger my-2 my-sm-0" type="submit">Cerrar sesión</button>
</div>
</nav>
<div class="container in-session">
<div class="row">
<div class="col-md-10 offset-md-1">
<h2>Perfil</h2> <h2>Perfil</h2>
<input id="webid" size="60"> <button id="view">Mostrar</button> <div class="input-group mb-3">
<p> <input id="webid" class="form-control">
<label>Nombre:</label> <div class="input-group-append">
<span id="fullName"></span> <button id="view" class="btn btn-primary">Mostrar</button>
</p> </div>
<p id="photo"></p> </div>
</div>
<h2>Contactos</h2> </div>
<div class="row">
<div id="photo" class="col-md-2 offset-md-1"></div>
<div class="col-md-2">
<h4 id="fullName"></h4>
<span id="email"></span>
</div>
<div class="col-md-4 offset-md-1">
<h4>Contactos</h4>
<ul id="friends"></ul> <ul id="friends"></ul>
<input id="newFriend" size="60"/> <button id="addFriend">Añadir</button> <div class="input-group mb-3">
<input id="newFriend" class="form-control" placeholder="Introduce WebID">
<div class="input-group-append">
<button id="addFriend" class="btn btn-success"><svg class="bi bi-person-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M11 14s1 0 1-1-1-4-6-4-6 3-6 4 1 1 1 1h10zm-9.995-.944v-.002.002zM1.022 13h9.956a.274.274 0 00.014-.002l.008-.002c-.001-.246-.154-.986-.832-1.664C9.516 10.68 8.289 10 6 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664a1.05 1.05 0 00.022.004zm9.974.056v-.002.002zM6 7a2 2 0 100-4 2 2 0 000 4zm3-2a3 3 0 11-6 0 3 3 0 016 0zm4.5 0a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13V5.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 7.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-10 offset-md-1">
<h4>Almacenamiento</h4>
<div class="input-group mb-3">
<input id="container" class="form-control">
<div class="input-group-append">
<button id="updateContainer" class="btn btn-primary">Actualizar</button>
</div>
</div>
</div>
</div>
<h2>POD</h2> <div class="row">
<input id="container" size="60" /> <button id="updateContainer">Actualizar</button> <div class="col-md-5 offset-md-1">
<ul id="items"></ul> <ul id="items"></ul>
<input id="newContainer" size="60"/> <button id="addContainer">Crear carpeta</button>
</div> </div>
<div class="col-md-3 offset-md-1">
<label>Crear nuevo recurso:</label>
<div class="input-group mb-3">
<input id="newContainer" class="form-control" placeholder="Nombre de la carpeta">
<div class="input-group-append">
<button id="addContainer" class="btn btn-success"><svg class="bi bi-folder-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M9.828 4H2.19a1 1 0 00-.996 1.09l.637 7a1 1 0 00.995.91H9v1H2.826a2 2 0 01-1.991-1.819l-.637-7a1.99 1.99 0 01.342-1.31L.5 3a2 2 0 012-2h3.672a2 2 0 011.414.586l.828.828A2 2 0 009.828 3h3.982a2 2 0 011.992 2.181L15.546 8H14.54l.265-2.91A1 1 0 0013.81 4H9.828zm-2.95-1.707L7.587 3H2.19c-.24 0-.47.042-.684.12L1.5 2.98a1 1 0 011-.98h3.672a1 1 0 01.707.293z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13.5 10a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13v-1.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 12.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<div class="input-group mb-3">
<input id="newRdf" class="form-control" placeholder="Nombre del archivo .ttl">
<div class="input-group-append">
<button id="addRdf" class="btn btn-success"><svg class="bi bi-file-earmark-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1H4a2 2 0 00-2 2v10a2 2 0 002 2h5v-1H4a1 1 0 01-1-1V3a1 1 0 011-1h5v2.5A1.5 1.5 0 0010.5 6H13v2h1V6L9 1z"/>
<path fill-rule="evenodd" d="M13.5 10a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13v-1.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 12.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<label>Eliminar recurso:</label>
<div class="input-group mb-3">
<input id="deleteUri" class="form-control" placeholder="URI del recurso">
<div class="input-group-append">
<button id="delete" class="btn btn-danger"><svg class="bi bi-file-earmark-minus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1H4a2 2 0 00-2 2v10a2 2 0 002 2h5v-1H4a1 1 0 01-1-1V3a1 1 0 011-1h5v2.5A1.5 1.5 0 0010.5 6H13v2h1V6L9 1z"/>
<path fill-rule="evenodd" d="M11 11.5a.5.5 0 01.5-.5h4a.5.5 0 010 1h-4a.5.5 0 01-.5-.5z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<label>Hacer privado:</label>
<div class="input-group mb-3">
<input id="blockUri" class="form-control" placeholder="URI del recurso">
<div class="input-group-append">
<button id="block" class="btn btn-warning"><svg class="bi bi-lock" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M11.5 8h-7a1 1 0 00-1 1v5a1 1 0 001 1h7a1 1 0 001-1V9a1 1 0 00-1-1zm-7-1a2 2 0 00-2 2v5a2 2 0 002 2h7a2 2 0 002-2V9a2 2 0 00-2-2h-7zm0-3a3.5 3.5 0 117 0v3h-1V4a2.5 2.5 0 00-5 0v3h-1V4z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
</div>
</div>
</div> <!-- / .container -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="js/solid-auth-client.bundle.js"></script> <script src="js/solid-auth-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/solid-file-client@1.0.0/dist/window/solid-file-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rdflib@1.1.0/dist/rdflib.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/rdflib@1.1.0/dist/rdflib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
/* ------------------------------------ /* -----------------------------------------
* Gestión de sesiones Inicialización
* ------------------------------------ */ */
$('#login button').click(() => popupLogin());
$('#logout #logoutButton').click(() => solid.auth.logout());
/* Login */
async function popupLogin() {
let session = await solid.auth.currentSession();
let popupUri = 'https://solidcommunity.net/common/popup.html';
if (!session)
session = await solid.auth.popupLogin({ popupUri });
}
solid.auth.trackSession(session => {
const loggedIn = !!session;
$('#login').toggle(!loggedIn);
$('#logout').toggle(loggedIn);
if (session) {
$('#user').text(session.webId);
if (!$('#webid').val())
$('#webid').val(session.webId);
}
});
/* ------------------------------------
* Recuperación de datos
* ------------------------------------ */
// Establecemos espacios de nombres
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#');
const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#');
// Creamos un almacén y le asociamos un lector RDF // Creamos un almacén y le asociamos un lector RDF
const store = $rdf.graph(); const store = $rdf.graph();
const fetcher = new $rdf.Fetcher(store); const fetcher = new $rdf.Fetcher(store);
$('#view').click(() => showProfile()); // Establecemos espacios de nombres
$('#updateContainer').click(() => showContainer($('#container').val())); const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#');
const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#');
const DC = $rdf.Namespace('http://purl.org/dc/terms/');
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
/* -------------- // Definimos función para invocar todas subvistas
Mostramos información del perfil referenciado por la WebID function showProfile(person) {
*/
async function showProfile() {
// Cargamos los datos a partir del WebID
const person = $('#webid').val();
await fetcher.load(person);
showPersonalData(person); showPersonalData(person);
showFriends(person); showFriends(person);
var patt = /https:\/\/[^\/]+\//; var patt = /https:\/\/[^\/]+\//;
...@@ -50,91 +22,195 @@ async function showProfile() { ...@@ -50,91 +22,195 @@ async function showProfile() {
showContainer(rootContainer); showContainer(rootContainer);
} }
// Información personal /* ---------------------------------------
Gestión de sesiones
*/
const popupUri = 'https://solidcommunity.net/common/popup.html';
localStorage.clear();
$('#loginButton').click(() => solid.auth.popupLogin({ popupUri }));
$('#logoutButton').click(() => solid.auth.logout());
const fc = new SolidFileClient(solid.auth);
solid.auth.trackSession(async session => {
const loggedIn = !!session;
$('.out-session').toggle(!loggedIn);
$('.in-session').toggle(loggedIn);
if (session) {
$('#user').text(session.webId);
$('#webid').val(session.webId);
await fetcher.load(session.webId);
showProfile(session.webId);
}
});
/* ------------------------------------
Información del perfil
*/
function showPersonalData(person) { function showPersonalData(person) {
// Mostramos tres campos del perfil // Mostramos tres campos del perfil
const fullName = store.any($rdf.sym(person), VCARD('fn')); const fullName = store.any($rdf.sym(person), FOAF('name'));
const photoUrl = store.any($rdf.sym(person), VCARD('hasPhoto')); const photoUrl = store.any($rdf.sym(person), VCARD('hasPhoto'));
const email_node = store.any($rdf.sym(person), VCARD('hasEmail'));
const email = store.any(email_node, VCARD('value'));
$('#fullName').text(fullName && fullName.value); $('#fullName').text(fullName && fullName.value);
$('#photo').html($('<img>').attr('src', photoUrl && photoUrl.value).attr('width', '200px')); $('#email').html($('<a>').attr('href', email && email.value).text(email && email.value));
$('#photo').html($('<img>').attr('src', photoUrl && photoUrl.value).attr('width', '160px'));
} }
/* ------------------------- /* ------------------------------------
Contactos (navegable) Contactos (navegable)
*/ */
async function showFriends(person) { async function showFriends(person) {
// Mostramos su lista de amigos // Mostramos su lista de amigos
const friends = store.each($rdf.sym(person), FOAF('knows')); const friends = store.each($rdf.sym(person), FOAF('knows'));
$('#friends').empty(); $('#friends').empty();
friends.forEach(async (friend) => { friends.forEach(async (friend) => {
await fetcher.load(friend); // con cada "load", la base de conocimiento ¡crece! await fetcher.load(friend); // cada vez que hacemos "load", nuestra base de conocimiento ¡crece!
const fullName = store.any(friend, FOAF('name')); const fullName = store.any(friend, FOAF('name'));
$('#friends').append( $('#friends').append(
$('<li>').append( $('<li>').append(
$('<a>').text((fullName && fullName.value || friend.value)) $('<button>').text((fullName && fullName.value || friend.value))
.addClass('btn btn-link')
.click(() => $('#webid').val(friend.value)) .click(() => $('#webid').val(friend.value))
.click(() => showProfile(friend.value)))); .click(() => showProfile(friend.value))));
}); });
} }
/* -----------------------------------------------------
Cargamos perfil
*/
$('#view').click(async function () {
// Cargamos los datos a partir del WebID
const person = $('#webid').val();
await fetcher.load(person);
showProfile(person);
});
/** -----------------------------------------------
* Añadimos un nuevo amigo
*/
$('#addFriend').click(async function () {
person = store.sym($('#webid').val()); // nodos
friend = store.sym($('#newFriend').val());
profile = person.doc(); // este es el documento a actualizar (card)
store.add(person, FOAF('knows'), friend, profile);
await fetcher.putBack(profile); // actualizamos documento en la nube
$('#newFriend').val('');
showFriends(person);
});
/* ----------------------------------------------- /* -----------------------------------------------
Contenido del POD (navegable) Contenido del POD (navegable)
*/ */
async function showContainer(container) { async function showContainer(container) {
// Cargamos datos desde el POD // Cargamos el contenido del POD
await fetcher.load(container); await fetcher.load(container);
$('#container').val(container); $('#container').val(container);
// Mostramos elementos // Mostramos elementos
const items = store.each($rdf.sym(container), LDP('contains')); const items = store.each($rdf.sym(container), LDP('contains'));
$('#items').empty(); $('#items').empty();
items.forEach(async (item) => { items.forEach(async (item) => {
if (item.value[item.value.length - 1] == '/') text = item.value.replace(container, ''); // eliminamos ruta
element = $('<a>').text(decodeURI(item.value)) if (item.value[item.value.length-1] == '/')
element = $('<button>').text(decodeURI(text))
.addClass('btn btn-outline-primary')
.click(() => $('#container').val(item.value)) .click(() => $('#container').val(item.value))
.click(() => showContainer(item.value)); .click(() => showContainer(item.value));
else else
element = $('<a>').text(decodeURI(item.value)) element = $('<a>').text(decodeURI(text))
.addClass('btn btn-link')
.attr('href', item.value) .attr('href', item.value)
.attr('target', 'blank'); .attr('target', 'blank');
$('#items').append($('<li>').append(element)); $('#items').append($('<li>').append(element));
}); });
} }
/*-------------------------------------------------------
Actualizamos POD
*/
$('#updateContainer').click(async function () {
fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
/* -------------------------------------------------- /* --------------------------------------------------
Añadimos una nueva carpeta (contenedor) Añadimos una nueva carpeta (contenedor)
*/ */
$('#addContainer').click(async function () { $('#addContainer').click(async function () {
await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null); await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null);
$('#newContainer').val(''); $('#newContainer').val('');
fetcher.refresh($('#container').val())
showContainer($('#container').val()); showContainer($('#container').val());
}); });
/* ----------------------------------------------- /* --------------------------------------------------
* Añadimos un nuevo amigo Consultas con SPARQL
*/ */
$('#addFriend').click(async function () {
person = store.sym($('#webid').val()); // nodos
friend = store.sym($('#newFriend').val());
profile = person.doc(); // este es el documento a actualizar (card)
store.add(person, FOAF('knows'), friend, profile);
await fetcher.putBack(profile); // actualizamos documento en la nube
$('#newFriend').val('');
showFriends(person);
});
/* /*
Para probar en la consola // ¿Quién conoce al usuario con este WebId?
sparql_query = " \
PREFIX foaf: <http://xmlns.com/foaf/0.1/> \
SELECT ?who WHERE { \
?who foaf:knows <" + $('#webid').val() + "> . \
}";
query_sparql = "PREFIX foaf: <http://xmlns.com/foaf/0.1/> \ // Generamos consulta para rdflib
SELECT ?who WHERE { \ query = $rdf.SPARQLToQuery(sparql_query, false, store);
?who foaf:knows <" . $(#webid).val() . " > . \
}";
query = $rdf.SPARQLToQuery(query_sparql, false, store); // Ejecutamos consulta y mostramos en consola cada resultado encontrado
store.query(query, (bindings) => console.log(bindings['?who'].value));
*/
store.query(query, function (bindings) { /* --------------------------------------------------
console.log(bindings['?who'].value); Añadimos un nuevo recurso con información RDF
*/
$('#addRdf').click(async function () {
doc = store.sym($('#container').val() + $('#newRdf').val());
store.add(doc, DC('creator'), $('#webid').val(), doc);
await fetcher.putBack(doc);
$('#newRdf').val('');
fetcher.refresh($('#container').val())
showContainer($('#container').val());
}); });
/* -----------------------------------------------------
Eliminamos recurso a partir de su URI
*/ */
$('#delete').click(async function () {
if (confirm("¿Seguro que quiere borrar " + $('#deleteUri').val() +"?")) {
await fetcher.delete($('#deleteUri').val());
await fetcher.refresh($('#container').val());
$('#deleteUri').val('');
showContainer($('#container').val());
}
});
/* -----------------------------------------------------
Hacemos el recurso solo accesible por el propietario
*/
$('#block').click(async function () {
owner = $('#webid').val();
uri = $('#blockUri').val();
rdf = `
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
<#owner>
a acl:Authorization;
acl:agent <${owner}>;
acl:accessTo <${uri}>;
acl:mode acl:Read, acl:Write, acl:Control.
`;
console.log(rdf);
await fc.createFile($('#blockUri').val() + '.acl', rdf, 'text/turtle');
$('#blockUri').val('');
await fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment